在编译系统中,linker(ld)怎么知道要linkmyprogram.o给谁呢?
In the compilation system, how does linker(ld) know who to link myprogram.o to?
最近看了CSAPP,对其中的编译系统部分有些疑惑。
现在我们有一个使用 HelloWorld.c 的示例(只需打印 hello world)。书中说在预处理器阶段,他们用这个头文件的内容替换了“#include”行。但是打开stdio.h,发现里面只有printf()的声明,并没有具体的实现。那么在编译系统中,什么时候引入printf()的具体实现呢?
而且书中还说,在 linking 阶段,linker(ld) linked helloworld.o 和 printf.o 。为什么 linker 知道 link 我的目标文件到 printf.o?为什么编译系统在第一步(Pre-processor phase)声明这个函数,link在最后一步(linking phase)具体实现?
为什么 linker 知道 link 我的目标文件到 printf.o?
LD 知道如何搜索和找到它们。可以看到with man ld.so:
If a shared object dependency does not contain a slash, then it is
searched for in the following order:
- Using the directories specified in the DT_RPATH dynamic section attribute of the binary if present and DT_RUNPATH attribute does not
exist. Use of DT_RPATH is deprecated.
- Using the environment variable LD_LIBRARY_PATH, unless the executable is being run in secure-execution mode (see below), in which
case this variable is ignored.
- Using the directories specified in the DT_RUNPATH dynamic section attribute of the binary if present. Such directories are searched only
to find those objects required by DT_NEEDED (direct dependencies)
entries and do not apply to those objects' children, which must
themselves have their own DT_RUNPATH entries. This is unlike DT_RPATH,
which is applied to searches for all children in the dependency tree.
- From the cache file /etc/ld.so.cache, which contains a compiled list of candidate shared objects previously found in the augmented
library path. If, however, the binary was linked with the -z nodeflib
linker option, shared objects in the default paths are skipped. Shared
objects installed in hardware capability directories (see below) are
preferred to other shared objects.
- In the default path /lib, and then /usr/lib. (On some 64-bit architectures, the default paths for 64-bit shared objects are /lib64,
and then /usr/lib64.) If the binary was linked with the -z nodeflib
linker option, this step is skipped.
编译系统为什么在第一步(Pre-processor阶段)声明这个函数,link最后一步(link具体实现ing阶段)?
在编译阶段,你需要知道你要link做什么并相应地编译,所以它需要阅读.h
文件的定义。在linking阶段,只需要.o
个文件。
Why the linker knows to link my object file to printf.o
因为编译器在它生成的文件中记录了这一点,通常称为目标文件 (.o)。
why does it declare this function in the first step ...
了解一下。
... and link the concrete implementation in the last step
因为没有必要早点这样做。
所有 C 和 C++ 标准告诉您的是,您需要 #include
给定的头文件以引入某些功能(在某些平台上,这甚至可能不是必需的,尽管包含是一个好主意,因为那么你正在编写可移植代码。
这为编译器提供了很大的灵活性。
链接(如果有)将自动完成。请注意,某些函数甚至可能被硬编码到编译器本身。
默认情况下,库(包含 printf 的实现)在您的 C 程序中每次都被链接。
通过包含 header,您只需在编译时指定(暂时)声明的函数(在 header 内)的实现在其他地方。稍后在链接阶段,这些函数实现在您的代码中 'added'。
实际上,over-simplified:
- 您可以将函数编译成库(例如 unix 上的
.a
或 .so
文件)。
- 该库有一个函数body(汇编指令)和一个函数名。前任。库
libc.so
具有 printf
函数,该函数从库文件 libc.so
. 中的字符编号 0xaabbccdd
开始
- 你想编译你的程序。
- 您需要知道
printf
需要哪些参数。需要 int
吗?需要char *
吗?需要uint_least64_t
吗?它在 header 文件中 - int printf(const char *, ...);
。 header 告诉编译器如何调用该函数(函数采用什么参数以及它的类型 returns)。请注意,每个 .c
文件都是单独编译的。
- 函数声明(函数接受哪些参数以及函数的作用return)未存储在库文件中。它存储在 header 中(仅)。该库有函数名(仅
printf
)和编译函数body。 header 有 int printf(const char *, ...);
没有函数 body.
- 你编译你的程序。编译器生成代码,以便将具有适当大小的参数压入堆栈。从堆栈中,您的代码从函数中获取变量 returned。现在您的程序已编译成看起来像
push pointer to "%d\n" on the stack; push some int on the stack; call printf; pop from the stack the returned "int"; rest of the instructions;
. 的程序集
- 链接器搜索您编译的程序,它看到
call printf
。然后它说:"Och, there is no printf
body in your code"。然后它会在库中搜索 printf
,看看它在哪里。 linker 遍历了你的程序使用的所有库 link,它在标准库中找到了 printf
- 它在地址 0xaabbccdd
的 libc.so
中。因此 linker 将 call printf
替换为 goto libs.so file to address 0xaabbccdd
kind-of 指令。
- 毕竟"symbols"(即函数名,变量名)是"resolved"(linker已经在某处找到了),那么你可以运行你的程序。
call printf
将跳转到指定位置的文件 libc.so
。
我上面写的只是为了说明目的。
最近看了CSAPP,对其中的编译系统部分有些疑惑。
现在我们有一个使用 HelloWorld.c 的示例(只需打印 hello world)。书中说在预处理器阶段,他们用这个头文件的内容替换了“#include”行。但是打开stdio.h,发现里面只有printf()的声明,并没有具体的实现。那么在编译系统中,什么时候引入printf()的具体实现呢?
而且书中还说,在 linking 阶段,linker(ld) linked helloworld.o 和 printf.o 。为什么 linker 知道 link 我的目标文件到 printf.o?为什么编译系统在第一步(Pre-processor phase)声明这个函数,link在最后一步(linking phase)具体实现?
为什么 linker 知道 link 我的目标文件到 printf.o?
LD 知道如何搜索和找到它们。可以看到with man ld.so:
If a shared object dependency does not contain a slash, then it is searched for in the following order:
- Using the directories specified in the DT_RPATH dynamic section attribute of the binary if present and DT_RUNPATH attribute does not exist. Use of DT_RPATH is deprecated.
- Using the environment variable LD_LIBRARY_PATH, unless the executable is being run in secure-execution mode (see below), in which case this variable is ignored.
- Using the directories specified in the DT_RUNPATH dynamic section attribute of the binary if present. Such directories are searched only to find those objects required by DT_NEEDED (direct dependencies) entries and do not apply to those objects' children, which must themselves have their own DT_RUNPATH entries. This is unlike DT_RPATH, which is applied to searches for all children in the dependency tree.
- From the cache file /etc/ld.so.cache, which contains a compiled list of candidate shared objects previously found in the augmented library path. If, however, the binary was linked with the -z nodeflib linker option, shared objects in the default paths are skipped. Shared objects installed in hardware capability directories (see below) are preferred to other shared objects.
- In the default path /lib, and then /usr/lib. (On some 64-bit architectures, the default paths for 64-bit shared objects are /lib64, and then /usr/lib64.) If the binary was linked with the -z nodeflib linker option, this step is skipped.
编译系统为什么在第一步(Pre-processor阶段)声明这个函数,link最后一步(link具体实现ing阶段)?
在编译阶段,你需要知道你要link做什么并相应地编译,所以它需要阅读.h
文件的定义。在linking阶段,只需要.o
个文件。
Why the linker knows to link my object file to printf.o
因为编译器在它生成的文件中记录了这一点,通常称为目标文件 (.o)。
why does it declare this function in the first step ...
了解一下。
... and link the concrete implementation in the last step
因为没有必要早点这样做。
所有 C 和 C++ 标准告诉您的是,您需要 #include
给定的头文件以引入某些功能(在某些平台上,这甚至可能不是必需的,尽管包含是一个好主意,因为那么你正在编写可移植代码。
这为编译器提供了很大的灵活性。
链接(如果有)将自动完成。请注意,某些函数甚至可能被硬编码到编译器本身。
默认情况下,库(包含 printf 的实现)在您的 C 程序中每次都被链接。
通过包含 header,您只需在编译时指定(暂时)声明的函数(在 header 内)的实现在其他地方。稍后在链接阶段,这些函数实现在您的代码中 'added'。
实际上,over-simplified:
- 您可以将函数编译成库(例如 unix 上的
.a
或.so
文件)。 - 该库有一个函数body(汇编指令)和一个函数名。前任。库
libc.so
具有printf
函数,该函数从库文件libc.so
. 中的字符编号 - 你想编译你的程序。
- 您需要知道
printf
需要哪些参数。需要int
吗?需要char *
吗?需要uint_least64_t
吗?它在 header 文件中 -int printf(const char *, ...);
。 header 告诉编译器如何调用该函数(函数采用什么参数以及它的类型 returns)。请注意,每个.c
文件都是单独编译的。 - 函数声明(函数接受哪些参数以及函数的作用return)未存储在库文件中。它存储在 header 中(仅)。该库有函数名(仅
printf
)和编译函数body。 header 有int printf(const char *, ...);
没有函数 body. - 你编译你的程序。编译器生成代码,以便将具有适当大小的参数压入堆栈。从堆栈中,您的代码从函数中获取变量 returned。现在您的程序已编译成看起来像
push pointer to "%d\n" on the stack; push some int on the stack; call printf; pop from the stack the returned "int"; rest of the instructions;
. 的程序集
- 链接器搜索您编译的程序,它看到
call printf
。然后它说:"Och, there is noprintf
body in your code"。然后它会在库中搜索printf
,看看它在哪里。 linker 遍历了你的程序使用的所有库 link,它在标准库中找到了printf
- 它在地址0xaabbccdd
的libc.so
中。因此 linker 将call printf
替换为goto libs.so file to address 0xaabbccdd
kind-of 指令。 - 毕竟"symbols"(即函数名,变量名)是"resolved"(linker已经在某处找到了),那么你可以运行你的程序。
call printf
将跳转到指定位置的文件libc.so
。
0xaabbccdd
开始
我上面写的只是为了说明目的。