在编译系统中,链接器(ld)如何知道将myprogram.o链接到谁
In the compilation system, how does linker (ld) know who to link myprogram.o to?
我最近读了CSAPP,对它的编译系统部分有一些疑问。
现在我们有一个使用HelloWorld.c的示例(只打印HelloWorld)。书中说,在预处理阶段,他们用这个头文件的内容替换"#include"行。但是当我打开stdio.h时,我发现只有printf()的声明,并且没有具体的实现。那么在编译系统中,什么时候会引入printf()的具体实现呢?
书中还说,在链接阶段,链接器(ld)链接了helloworld.o和printf.o。为什么链接器知道要将我的对象文件链接到printf.o?在编译系统中,为什么它在第一步(预处理器阶段)声明这个函数,并在最后一步(链接阶段)链接具体实现?
实际上,过于简化:
- 您可以将函数编译到库中(例如unix上的
.a
或.so
文件) - 该库有一个函数体(汇编指令)和一个函数名。例如,库
libc.so
具有从库文件libc.so
中的字符号0xaabbccdd
开始的printf
功能 - 你想编译你的程序
- 您需要知道
printf
采用了哪些参数。需要int
吗?需要char *
吗?需要uint_least64_t
吗?它在头文件int printf(const char *, ...);
中。头告诉编译器如何调用函数(函数采用什么参数,返回什么类型)。请注意,每个.c
文件都是单独编译的 - 函数声明(函数采用的参数和返回的参数)不存储在库文件中。它存储在标头中(仅限)。该库具有函数名(仅
printf
)和已编译的函数体。标头具有int printf(const char *, ...);
,但没有函数体 - 你编译你的程序。编译器生成代码,以便将大小合适的参数推送到堆栈上。代码从堆栈中获取函数返回的变量。现在,您的程序已编译成类似
push pointer to "%dn" on the stack; push some int on the stack; call printf; pop from the stack the returned "int"; rest of the instructions;
的程序集 - Linker搜索您编译的程序,它会看到
call printf
。然后它会说:"哦,你的代码中没有printf
体"。然后它在库中搜索printf
,看看它在哪里。链接器遍历所有链接程序的库,在标准库中找到printf
——它在地址0xaabbccdd
的libc.so
中。所以链接器用call printf
代替了goto libs.so file to address 0xaabbccdd
类指令 - 在所有"符号"(即函数名、变量名)都被"解析"(链接器在某个地方找到了它们)之后,就可以运行程序了。
call printf
将跳转到指定位置的文件libc.so
中
我上面写的只是为了举例说明。
为什么链接器知道要将我的对象文件链接到printf.o
因为编译器在它生成的内容中注意到了这一点,通常称为对象文件(.o).
为什么它在第一步中声明这个函数。。。
了解它。
。。。并链接最后一步的具体实施
因为没有必要提前这样做。
所有的C和C++标准都告诉你,你需要#include
一个给定的头文件来引入一些功能(在一些平台上,这甚至可能不是必要的,尽管从那时起,包含是一个好主意,因为你正在编写可移植代码)。
这为编译器提供了很大的灵活性。
链接(如果有)将自动完成。请注意,有些函数甚至可能被硬编码到编译器本身中。
默认情况下,每次在C程序中都会链接库(包含printf的实现)。
通过包含头,您只需在编译时指定(暂时)声明函数的实现(在头内)在其他地方。稍后在链接阶段,这些函数实现将被"添加"到代码中。
为什么链接器知道要将我的对象文件链接到printf.o
LD知道如何搜索和找到它们。你可以看到与男子ld.so:
如果共享对象依赖项不包含斜杠,则为按以下顺序搜索:
- 使用二进制文件的DT_RPATH动态部分属性中指定的目录(如果存在),而DT_RUNPATH属性不存在存在不赞成使用DT_RPATH
- 使用环境变量LD_LIBRARY_PATH,除非可执行文件在安全执行模式下运行(见下文),其中如果忽略此变量
- 使用二进制文件的DT_RUNPATH动态节属性中指定的目录(如果存在)。仅搜索此类目录查找DT_NEEDED(直接依赖项)所需的对象项,并且不应用于这些对象的子对象,这些对象必须它们自身具有自己的DT_RUNPATH条目。这不同于DT_RPATH,其被应用于搜索依赖树中的所有子项
- 从缓存文件/etc/ld.so.cache中,该文件包含以前在增强的库路径。但是,如果二进制文件与-z nodeflib链接链接器选项,则跳过默认路径中的共享对象。共享安装在硬件功能目录中的对象(见下文)是优先于其他共享对象
- 在默认路径/lib中,然后是/usr/lib。(在一些64位体系结构上,64位共享对象的默认路径是/lib64,然后是/usr/lib64。)如果二进制文件与-z nodeflib链接链接器选项,则跳过此步骤
在编译系统中,为什么它在第一步(预处理器阶段)声明这个函数,并在最后一步(链接阶段)链接具体实现
在编译阶段,您需要知道要链接到什么并进行相应的编译,因此需要读取带有定义的.h
文件。在链接阶段,只需要.o
文件。
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- CMake-按正确顺序将项目与C运行时对象文件链接
- 从链接列表c++中删除一个项目
- 有根的二进制搜索树.保留与其父级的链接
- 读取文件的最后一行并输入到链接列表时出错
- 静态数据成员的问题-修复链接错误会导致编译器错误
- node-gyp 在 macOS 上未正确链接库
- 基于boost的程序的静态链接——zlib问题
- 无法链接 CMake 中的本地库
- 内联函数中具有内部链接的全局变量
- 链接阶段在Ubuntu上失败,但在MacOS上失败
- 使用gcc从静态链接的文件中查找可选符号
- 我可以将一个用clang c++11编译的对象与另一个用c++17编译的对象链接起来吗
- 将--whole archive链接器选项与CMake和具有其他库依赖项的库一起使用
- 下面是我为检测链接列表中的循环而制作的代码
- 有了gcc,是否可以链接库,但前提是它存在
- 使用C链接在函数内部创建C++模板
- Visual Studio mkl_link_tool.exe链接错误
- 尝试链接我的着色器时,我收到错误代码"error c5145 must write to gl_position"
- 在编译系统中,链接器(ld)如何知道将myprogram.o链接到谁