什么时候printf()和scanf()函数被静态或动态链接到应用程序中?

8
当一个C程序被编译时,它会按照预处理器、编译器、汇编器和链接器的顺序进行处理。链接器的主要任务之一是使库函数的代码可用于您的程序。链接器可以以静态或动态的方式链接它们。
stdio.h只包含声明,没有定义。我们只在程序中包含stdio.h来告诉编译器函数的返回类型和名称,例如printf(),scanf(),getc()和putc()等。那么,在下面的示例程序中,如何链接printf()和scanf()呢?
- 如果它是动态链接,那么哪个“DLL”负责链接? - 整个“C”库是否都与程序动态链接?
    #include "stdio.h"
 
    int main()
    {
      int n;
 
       printf("Enter an integer\n");
       scanf("%d", &n);
 
       if (n%2 == 0)
           printf("Even\n");
       else
           printf("Odd\n");
 
       return 0;
    } 

1
与链接 C 运行时(标准库)的方式相同。 - Ivan Aksamentov - Drop
1
你的C程序有很多东西,将来应该避免使用。1)int main(){} 应该改为 **int main(void){}**。2)你应该检查 scanf() 是否出错。3)为什么,我重复一遍,为什么你需要在这里使用 \n =>> printf("Enter an integer\n");?4)你不使用if-else中的大括号有任何理由吗? - Michi
1
@Michi 1) 正确的签名是 int main(int argc, char *argv[]), 4) 在 if 语句中为单行代码添加括号只会浪费垂直空间并且会使代码变得杂乱无章。这只是一个风格问题,人们可以自由选择使用哪种方式(但无论您喜欢哪种风格,都需要修复缩进)。 - Carey Gregory
1
@CareyGregory如果没有参数怎么办?标准规定是 int main(void){} 或者 **int main(int argc, char *argv[]){}**。为什么只使用 *int main(int argc, char argv[]){} 请解释一下。 - Michi
1
@CareyGregory 关于括号,我本来是想知道OP的答案的,但如果你坚持的话,那么让我告诉你,仅仅因为C语言中有可选项并不意味着你不应该使用它们。我知道在那段代码中忽略它们没有问题,但在某些时候会有问题。我认为人们在分享他们的代码时应该更加尊重它,但这只是我的个人观点。 - Michi
显示剩余6条评论
4个回答

19

我认为你想问的问题是:“我知道像printfscanf这样的函数是由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,它几乎可以是内核组件)。


5
通常情况下,标准C库是动态链接的。这主要是因为一旦程序被静态链接,其中的代码就永远固定了。如果有人找到并修复了printfscanf中的错误,那么每个程序都必须重新链接以获取修复后的代码。
在动态链接的情况下,所有可执行文件(链接后创建的)都不包含printfscanf代码的副本。如果有新的修复版printf可用,则会在运行时获取它。

1
谢谢回复。如果它是动态链接的,哪个“DLL”负责链接?整个“C”库是否动态链接到程序中? - user3138495
1
@LearNer; .如果它是动态链接的,哪个“DLL”负责链接??:我对此一无所知。 整个“C”库是否动态链接到程序中??:我说的是通常情况。我不能确定地说。C编译器的作者/开发人员可以更详细地解释这些问题。我只是给你一个大致的概念。 - haccks
1
如果一个实现捆绑并静态链接函数,如printfscanf等,则可以让程序员从不同版本的函数中选择最适合其特定用例的版本。虽然能够修复一些C运行时错误或怪癖可能很好,但角落情况的行为突然改变通常不是一件好事。 - supercat
我认为动态链接的主要原因是你不想在磁盘上和内存中将那些函数的代码复制一百万次和一千次。标准库只存在一次(好吧,也许每个版本都有一次),分别存在于磁盘和RAM中。 - Peter - Reinstate Monica

4
-static-libstdc++

使用g++程序链接C++程序时,通常会自动链接libstdc++库。如果libstdc++作为共享库可用,并且未使用-static选项,则链接到libstdc++的共享版本。这通常没问题。但是,有时候需要冻结程序使用的libstdc++版本,而不必完全进行静态链接。-static-libstdc++选项指示g++驱动程序在不一定链接其他库的情况下静态链接libstdc++。详情请查看此主题。 如何将标准库静态链接到我的C++程序中?

3
值得一提的是,本回答描述了GNU GCC编译器中GNU C++标准库的链接方法,该编译器和C++标准库是主流的,但并非独特的。此外,这与定义printf()scanf()函数的C库无关。 - Ivan Aksamentov - Drop

-3

它们被静态链接,这样你的程序就能在运行之前确定是否存在任何编译错误。


5
错误。这取决于编译器/链接器的设置。另外,链接发生在编译之后,不能影响编译过程。 - Ivan Aksamentov - Drop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接