之前知道,如果想把多个.o
文件单纯地合并为一个大的.o
文件,可以通过链接器ld
的-r
选项来实现。man ld
中的描述如下:
-r
--relocatable
Generate relocatable output---i.e., generate an output file that
can in turn serve as input to ld. This is often called partial
linking. As a side effect, in environments that support standard
Unix magic numbers, this option also sets the output file's magic
number to "OMAGIC". If this option is not specified, an absolute
file is produced. When linking C++ programs, this option will not
resolve references to constructors; to do that, use -Ur.
When an input file does not have the same format as the output
file, partial linking is only supported if that input file does not
contain any relocations. Different output formats can have further
restrictions; for example some "a.out"-based formats do not support
partial linking with input files in other formats at all.
This option does the same thing as -i.
具体用法是ld a.o b.o -r -o all.o
而众所周知,存档管理工具ar
也具有类似的将多个.o
合并为一个文件(静态库)的功能,用法如下(具体参数的含义自行查阅文档):
ar rcs libx.a a.o b.o
就表面功能而言,似乎没有很大的区别:二者都是生成了.o
的集合,无非最终文件类型略有区别。
为了一探究竟,便做了如下小试验。
新建a.cpp
,里面只定义了一个函数void f()
新建b.cpp
,里面只定义了一个函数void g()
新建main.cpp
,里面只定义了函数main
,并在其中调用f();g();
通过gcc -c
命令分别将上述三个文件编译为a.o
b.o
main.o
调用链接器ld a.o b.o -r -o ab.o
调用存档工具ar rcs libab.a a.o b.o
然后分别执行
g++ a.o b.o main.cpp -o main_a_b
g++ ab.o main.cpp -o main_abo
g++ main.o libab.a -o main_ab
三者均成功生成了对应的最终文件,且通过diff
比较,三者完全相同。
通过命令readelf -a
完整地查看ab.o
和libab.a
的内部结构,可以看到两者的最大区别是,前者只有一个经过合并的.symtab
,意味着不能删除或修改已有的.o
模块;而后者为每个.o
分配了独立的.symtab
,使之能够自由地管理这些存档。
到此可以知道,relocatable object
是将数个.o
的内容提取、汇总、合并后形成的标准.o
文件,而ar archive
则是维持各个.o
的原样,把它们并排放好,并记录下各自的信息,然后一同打包得到的文件。在库代码全部被使用的情况下,二者的表现没有差别。
而且gcc
对两者也显得非常宽容。两种文件除了均适用于上述gcc src -o dest
的形式,还适用于gcc -l{libname}
的形式。
cp ab.o libabo.a && g++ main.o -labo -L. -o main_libabo
g++ main.o -lab -L. -o main_libab
上述两条命令均可成功生成对应的最终文件,且二者完全相同。
当然较之于relocatable object
这样一整块的大块头,静态库文件在链接时只抽取用到的那部分.o
,这是前者无法做到的。不妨将main.c
中的g()
删除,只保留f()
,然后重复上面的所有指令。可以发现,所有libab.a
参与生成的最终文件都比较小,因为在链接时没有把b.o
加进去,这就体现了存档文件的优点。
当然这样通过依赖关系来选择性加入.o
的行为也会带来小小的困扰,即在执行编译命令时必须把依赖文件放在被依赖文件的前面;如在上例中,必须把main.o
写在前面,否则gcc
从前向后编译时,若先遇到libab.a
,发现其没有被任何地方用到,就直接把整个libab.a
舍弃了,之后再遇到main.o
时就会找不到f()
的定义。
如下面的两种写法就是会引起编译失败的:
g++ libab.a main.o -o main_ab
g++ -lab -L. main.o -o main_libab
我要评论