静态库只是一堆.o
文件。它们没有以任何有意义的方式“链接”;只是串联在一起。直到您执行真正的链接步骤才能解析符号。
.a
将 a与可执行文件链接和将等效的源代码复制到可执行文件的项目中基本上没有区别。因此,在此之前无需链接任何其他框架或库。
以下练习可能具有教育意义:
创建以下内容comptest.c
:
#include <stdio.h>
int main() {
printf("Hello world.\n");
return 0;
}
看看预处理器做了什么:
gcc -E comptest.c > comptest-cpp.c
这将删除#include
并用引用文件的内容替换它。这个文件是编译器实际看到的。
现在看看编译器做了什么(我在>
这里和下面使用语法,以便与 并行-E
):
gcc -S comptest.c > comptest.s
这是经过预处理和编译后生成的汇编语言。现在我们把它变成一个.o:
gcc -c comptest.c > comptest.o
现在让我们看看那个.o里面有什么:
$ nm comptest.o
0000000000000040 s EH_frame0
000000000000002d s L_.str
0000000000000000 T _main
0000000000000058 S _main.eh
U _puts
这里重要的是_main
和_puts
。_main
在此文件中的地址 0_puts
处定义。未定义。所以我们链接的东西最好提供它。让我们尝试在没有任何内容的情况下进行链接:
$ gcc -nodefaultlibs comptest.o
Undefined symbols for architecture x86_64:
"_exit", referenced from:
start in crt1.10.6.o
"_puts", referenced from:
_main in comptest.o
ld: symbol(s) not found for architecture x86_64
collect2: ld returned 1 exit status
(_exit
在 C 运行时中是隐含的;它没有在 .o 中直接引用)
好的,所以现在我们准备好将它们放在一起了。我们将明确:
gcc -nodefaultlibs comptest.o /usr/lib/libc.dylib -o comptest
comptest.o
这说要和动态库链接在一起libc
。它保证引用的每个符号都将由这些文件之一提供。它在生成的二进制文件中记下它应该动态加载符号/usr/lib/libc.dylib
(这是 libSystem.B.dylib 的符号链接,它本身是一个“伞形框架”而不是一个适当的库,但这有点超出你的需要在大多数情况下要知道;您可以假装它puts()
在 libSystem 中):
$ otool -L comptest
comptest:
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0)
如果您使用静态库链接,则与在命令行中列出其中包含的所有 .o 文件相同。
请注意,在链接步骤中,我们只有 .o 和 .dylib 文件(.a 只是 .o 的一个包)。没有 .c 文件,没有 .h 文件,没有 .s 文件,没有源代码。只是需要解析符号的目标文件。这就是为什么头文件在这里无关紧要,但在编译时却很重要。