前面我们学习了编译链接的一些知识,现在来看看静态库链接的一些知识~
静态库本质上就是使用ar命令打包一堆.o文件:
$ ar -r test.a myObj1.o myObj2.o
静态库没有标准,不同的linux下都会有些细微的差别。大致的格式:
Global header
----------------- +-------------------------------
File header 1 ---> | File name
File content 1 | | File modification timestamp
----------------- | Owner ID
File header 2 | Group ID
File content 2 | File mode
----------------- | File size in bytes
... | File magic
+-------------------------------
链接过程
链接器在链接静态库时,同链接一般的.o基本相似。链接过程大致如下:
所有传入链接器的.o都会被链接进最终的可执行程序;链接.o时,会将.o中的global symbol和unresolved symbol放入一个临时表。
如果多个.o定义了相同的global symbol,那么就会得到多重定义的链接错误。
如果链接结束了,unresolved symbol表不为空,那么就会得到符号未定义的链接错误。
.a静态库处理本质上就是处理其中的每一个.o,不同的是,如果某个.o中没有一个符号属于unresolved symbol表,链接器此时怀疑该.o没有必要,它就会被忽略。
一些例子
// test.cpp
#include <stdio.h>
class Test {
public:
Test() {
printf("Test ctor\n");
}
};
static Test s_test;
// lib.cpp
#include <stdio.h>
class Lib {
public:
Lib() {
printf("Lib ctor\n");
}
};
static Lib s_lib;
// main.cpp
#include <stdio.h>
int main() {
printf("main\n");
return 0;
}
以上代码main.cpp中未引用任何test.cpp和lib.cpp中的符号。
结果如下:
$ g++ -o test test.o lib.o main.o
$ ./test
Lib ctor
Test ctor
main
如果某个.o被链接进程序,那么其文件内的静态变量就会先于main初始化,上面代码中test.o和lib.o都被链接进来了。
但是如果把lib.o以静态库的形式进行链接,情况就不一样了:为了做对比,基于以上的代码再加一个文件,及修改main.cpp:
// libfn.cpp
int sum(int a, int b) {
return a + b;
}
// main.cpp
#include <stdio.h>
int main() {
printf("main\n");
extern int sum(int, int);
printf("sum: %d\n", sum(2, 3));
return 0;
}
将libfn.o和lib.o打包为静态库:
$ ar -r libfn.a libfn.o lib.o
$ g++ -o test main.o test.o -lfn -L.
$ ./test
Test ctor
main
sum: 5
main.cpp中未引用任何lib.cpp中的符号,所以lib.o没有被链接,导致其文件内的静态变量也未得到初始化。
调整链接顺序:
# 将libfn.a的链接放在main.o前面
$ g++ -o test test.o -lfn main.o -L.
main.o: In function `main':
main.cpp:(.text+0x19): undefined reference to `sum(int, int)'
collect2: ld returned 1 exit status
问题在于链接器在链接libfn.a的时候,发现libfn.o中的符号没有被之前链接的*.o引用到,也就是没有任何符号在unresolved symbol table中,所以libfn.o也被忽略。
一些参数
-whole-archive
-whole-archive选项告诉链接器把静态库中的所有.o都进行链接,针对以上例子:
$ g++ -o test -L. test.o -Wl,--whole-archive -lfn main.o -Wl,--no-whole-archive
$ ./test
Lib ctor
Test ctor
main
sum: 5
连lib.o也被链接了进来。-Wl选项告诉gcc将其作为链接器参数传入;之所以在命令行结尾加上–no-whole-archive是为了告诉编译器不要链接gcc默认的库。
–start-group
--start-group archives --end-group
位于–start-group –end-group中的所有静态库将被反复搜索,而不是默认的只搜索一次,直到不再有新的unresolved symbol产生为止。也就是说,出现在这里的.o如果发现有unresolved symbol,则可能回到之前的静态库中继续搜索。
$ g++ -o test -L. test.o -Wl,--start-group -lfn main.o -Wl,--end-group
$ ./test
Test ctor
main
sum: 5