7

当一个 C 程序被编译时,它按照预处理器、编译器、汇编器、链接器的顺序进行。的主要任务之一linker是使库函数的代码可用于您的程序。链接器可以以静态或动态两种方式链接它们。

stdio.h仅包含声明,其中没有定义。我们只stdio.h在程序中包含关于函数的返回类型和名称的编译器eg(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;
    } 
4

4 回答 4

16

我认为您要问的问题是:“我知道函数类似于C 运行时库printf并且scanf由 C 运行时库实现。但是我可以在不告诉我的编译器和/或 IDE 将我的程序与 C 运行时库链接的情况下使用它们。为什么我不需要这样做?”</p>

这个问题的答案是:“不需要与 C 运行时库链接的程序非常非常罕见。即使您没有明确使用任何库函数,您仍然需要启动代码,并且编译器可能会在“幕后”发出对memcpy、浮点仿真函数等的调用。因此,为方便起见,编译器会自动将您的程序与 C 运行时库链接,除非您告诉它要这样做。”</p>

您必须查阅编译器的文档以了解如何告诉它不要链接到 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,(略)大于libc.so+ld.so在我的 Linux 分区上。即使您是 Microsoft,编写仅使用的程序也很少且困难ntdll.dll(我确定的唯一示例是csrss.exe,它也可能是内核组件)。

于 2015-10-29T19:19:15.623 回答
5

通常,标准 C 库是动态链接的。这主要是因为程序一旦被静态链接,其中的代码就永远固定了。如果有人发现并修复了printfor中的错误scanf,则必须再次链接每个程序以获取已修复的代码。

在动态链接的情况下,任何可执行文件(在链接后创建)都不包含printfor的代码副本scanf。如果有新的、固定的版本printf可用,则在运行时获取它。

于 2015-10-29T19:50:28.463 回答
4
-static-libstdc++

当 g++ 程序用于链接 C++ 程序时,它通常会自动链接到 libstdc++。如果 libstdc++ 可用作共享库,并且未使用 -static 选项,则此链接针对 libstdc++ 的共享版本。这通常没问题。但是,有时冻结程序使用的 libstdc++ 版本而不是一直到完全静态的链接很有用。-static-libstdc++ 选项指示 g++ 驱动程序静态链接 libstdc++,而不必静态链接其他库。

有关更多详细信息,请查看此线程。 如何将标准库静态链接到我的 c++ 程序?

于 2015-10-29T19:05:30.813 回答
-3

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

于 2015-10-29T18:59:26.580 回答