我认为你想问的问题是:“我知道像printf
和scanf
这样的函数是由C运行时库实现的。但是,我可以在不告诉编译器和/或IDE链接我的程序与C运行时库的情况下使用它们。为什么我不需要那样做?”
这个问题的答案是:“几乎不需要与C运行时库链接的程序非常非常少。即使您没有显式使用任何库函数,您仍将需要启动代码,并且编译器可能会在“幕后”发出对memcpy
、浮点仿真函数等的调用。因此,为了方便起见,编译器会自动将您的程序与C运行时库链接,除非您告诉它不要这样做。”
您将需要查阅编译器文档,以了解如何告诉它不要链接C运行时库。GCC使用-nostdlib
命令行选项。以下,我演示了你需要跳过的步骤才能使其正常工作...
$ cat > test.c
#include <stdio.h>
int main(void) { puts("hello world"); return 0; }
^D
$ gcc -nostdlib test.c && { ./a.out; echo $?; }
/usr/bin/ld: warning: cannot find entry symbol _start
/tmp/cc8svIx5.o: In function ‘main’:
test.c:(.text+0xa): undefined reference to ‘puts’
collect2: error: ld returned 1 exit status
puts
显然在 C 库中,但是这个神秘的 "入口符号 _start
" 也在其中。关闭 C 库后,你也必须自己提供那个。
$ cat > test.c
int _start(void) { return 0; }
^D
$ gcc -nostdlib test.c && { ./a.out; echo $?; }
Segmentation fault
139
现在它链接了,但我们会得到一个分段错误,因为_start
没有返回的地方了! 操作系统期望它调用_exit
。 好的,让我们来做这件事...
$ cat > test.c
extern void _exit(int);
void _start(void) { _exit(0); }
^D
$ gcc -nostdlib test.c && { ./a.out; echo $?; }
/tmp/ccuDrMQ9.o: In function `_start':
test.c:(.text+0xa): undefined reference to `_exit'
collect2: error: ld returned 1 exit status
... 坚果,_exit
也是C运行库中的一个函数!原始系统调用时间...
$ cat > test.c
#include <unistd.h>
#include <sys/syscall.h>
void _start(void) { syscall(SYS_exit, 0); }
^D
$ gcc -nostdlib test.c && { ./a.out; echo $?; }
/tmp/cchtZnbP.o: In function `_start':
test.c:(.text+0x14): undefined reference to `syscall'
collect2: error: ld returned 1 exit status
...不对,syscall
也是C运行时库中的一个函数。我想我们只能使用汇编语言!
$ cat > test.S
#include <sys/syscall.h>
.text
.globl _start
.type _start, @function
_start:
movq $SYS_exit, %rax
movq $0, %rdi
syscall
$ gcc -nostdlib test.S && { ./a.out; echo $?; }
0
最终,在我的电脑上运行了。但是在不同的操作系统上,使用不同的系统调用程序集级规则时,它无法工作。
如果您必须降到汇编语言级别才能进行系统调用,那么您可能会想知道 -nostdlib
究竟有什么用处。它旨在用于编译完全独立的低级系统程序,例如引导加载程序、内核和(部分)C运行时本身-这些东西本来就必须自己实现所有内容。
如果我们再次从头开始做这件事,将完全分离出一个低级语言无关的运行时,其中只包括系统调用包装器、语言无关的进程启动代码以及任何语言的编译器可能需要在幕后调用的函数(例如memcpy
,_Unwind_RaiseException
,__muldi3
之类)。这个想法的问题是它很快会遭受任务蔓延的问题-你是否包括 errno
?通用线程原语? (哪些线程原语,具有哪些语义?)动态链接器?malloc
的实现,几个以上的东西都需要? Windows的ntdll.dll
就是从这个概念开始的,Windows 10上磁盘空间为1.8MB(略大于我Linux分区上的libc.so
+ld.so
)。即使你是微软,编写一个只使用ntdll.dll
的程序也很少见且难以编写(我确定的唯一一个例子是csrss.exe
,它几乎可以是内核组件)。
int main(int argc, char *argv[])
, 4) 在if
语句中为单行代码添加括号只会浪费垂直空间并且会使代码变得杂乱无章。这只是一个风格问题,人们可以自由选择使用哪种方式(但无论您喜欢哪种风格,都需要修复缩进)。 - Carey Gregory