在撰写本文时,Stackoverflow tag wiki on static libraries
告诉我们:
静态库是目标文件的存档。用作链接器输入,链接器提取它需要进行链接的目标文件。
所需的目标文件是那些为链接器提供符号定义的文件,它发现在其他输入文件中使用的符号没有定义。
需要的目标文件,而不是其他文件,从档案中提取出来并输入到链接中,就像它们是单独的输入文件一样
链接命令和静态库根本没有提到。
...
链接器通常会支持一个选项(GNU ld:--whole-archive,MS 链接:/WHOLEARCHIVE)来覆盖
静态库的默认处理,而是链接所有包含的目标文件,无论它们是否需要。
除了从中提取的目标文件之外,静态库对链接没有任何贡献,这些文件可能在不同的链接中有所不同。
它与共享库形成对比,共享库是另一种在链接中扮演完全不同角色的文件。
这应该清楚地说明--whole-archive 的作用。 --whole-archive 的范围一直持续到结束
链接器命令行或直到出现--no-whole-archive1。
默认情况下,链接器只会在链接器输入的命令行序列中出现该库的每个点检查静态库。
它不会为了解析它在以后的输入中发现的符号引用而重新检查静态库。
--start-group ... --end-group 对选项改变了默认行为。它指示链接器检查静态库
... 重复中提到的,按照这个顺序,只要这样做会产生新符号引用的任何新分辨率。 --start-group ... --end-group 对
链接器默认从... 中的静态库中选择目标文件。它将仅提取和链接它需要的目标文件,除非--whole-archive 也是
有效。
总结一下:-
--start-group lib0.a ... libN.a --end-group 告诉链接器:继续搜索 lib0.a ... libN.a 直到找不到更多目标文件为止。
--whole-archive lib0.a ... libN.a --no-whole-archive 告诉链接器:忘记你需要什么。只需链接所有lib0.a ... libN.a中的所有目标文件。
然后您可以看到,您可以通过--start-group lib0.a ... libN.a --end-group 成功建立的任何链接也将通过--whole-archive lib0.a ... libN.a --no-whole-archive 成功,
因为后者将链接所有必要的对象文件和所有不必要的对象文件,而无需费心去区分。
但事实并非如此。这是一个简单的例子:
x.c
#include <stdio.h>
void x(void)
{
puts(__func__);
}
y.c
#include <stdio.h>
void y(void)
{
puts(__func__);
}
main.c
extern void x(void);
int main(void)
{
x();
return 0;
}
编译所有源文件:
$ gcc -Wall -c x.c y.c main.c
制作一个静态库,归档x.o和y.o:
ar rcs libxy.a x.o y.o
尝试将程序与main.o 和libxy.a 链接顺序错误:
$ gcc -o prog libxy.a main.o
main.o: In function `main':
main.c:(.text+0x5): undefined reference to `x'
collect2: error: ld returned 1 exit status
失败是因为在main.o 中对x 的引用才被发现
链接器来不及在libxy.a(x.o) 中找到x 的定义。它达到了libxy.a
first 并没有找到它需要的目标文件。那时,它还没有关联
任何目标文件都放入程序中,因此需要解析 0 个符号引用。有
考虑了libxy.a,发现没用,不再考虑。
当然,正确的链接是:
$ gcc -o prog main.o libxy.a
但如果您没有意识到您只是将链接顺序从后到前,您可以
通过--whole-archive 获得成功链接:
$ gcc -o prog -Wl,--whole-archive libxy.a -Wl,--no-whole-archive main.o
$ ./prog
x
显然,你不能让它成功
$ gcc -o prog -Wl,--start-group libxy.a -Wl,--end-group main.o
main.o: In function `main':
main.c:(.text+0x5): undefined reference to `x'
collect2: error: ld returned 1 exit status
因为这与:
$ gcc -o prog libxy.a main.o
下面是一个默认行为失败的链接示例,但可以
通过--start-group ... --end-group 取得成功。
交流
#include <stdio.h>
void a(void)
{
puts(__func__);
}
b.c
#include <stdio.h>
void b(void)
{
puts(__func__);
}
ab.c
extern void b(void);
void ab(void)
{
b();
}
ba.c
extern void a(void);
void ba(void)
{
a();
}
abba.c
extern void ab(void);
extern void ba(void);
void abba(void)
{
ab();
ba();
}
main2.c
extern void abba(void);
int main(void)
{
abba();
return 0;
}
编译所有源代码:-
$ gcc -Wall a.c b.c ab.c ba.c abba.c main2.c
然后制作以下静态库:
$ ar rcs libbab.a ba.o b.o x.o
$ ar rcs libaba.a ab.o a.o y.o
$ ar rcs libabba.a abba.o
(请注意,那些旧目标文件x.o 和y.o 再次存档在这里)。
这里,libabba.a 依赖于 libbab.a 和 libaba.a。具体来说,
libabba.a(abba.o) 引用了ab,它在libaba.a(ab.o) 中定义;
它还引用了ba,它在libbab.a(ba.o) 中定义。
所以在链接顺序中,libabba.a 必须出现在 libbab.a 和 libaba.a 之前
而libbab.a 依赖于libaba.a。具体来说,libbab.a(ba.o) 做了一个
引用a,在libaba(a.o)中定义。
但libaba.a 也依赖于libbab.a。 libaba(ab.o) 做参考
到b,它在libbab(b.o) 中定义。之间存在循环依赖
libbab.a 和 libaba.a。因此,无论我们将其中哪一个放在默认链接中,它
将因未定义的引用错误而失败。无论哪种方式:
$ gcc -o prog2 main2.o libabba.a libaba.a libbab.a
libbab.a(ba.o): In function `ba':
ba.c:(.text+0x5): undefined reference to `a'
collect2: error: ld returned 1 exit status
或者那样:
$ gcc -o prog2 main2.o libabba.a libbab.a libaba.a
libaba.a(ab.o): In function `ab':
ab.c:(.text+0x5): undefined reference to `b'
collect2: error: ld returned 1 exit status
循环依赖是--start-group ... --end-group的解决方案:
$ gcc -o prog2 main2.o libabba.a -Wl,--start-group libbab.a libaba.a -Wl,--end-group
$ ./prog2
b
a
因此--whole-archive ... --no-whole-archive也是一种解决方案:
$ gcc -o prog2 main2.o libabba.a -Wl,--whole-archive libbab.a libaba.a -Wl,--no-whole-archive
$ ./prog2
b
a
但这是一个糟糕的解决方案。让我们跟踪哪些目标文件实际上是链接的
在每种情况下进入程序。
与--start-group ... --end-group:
$ gcc -o prog2 main2.o libabba.a -Wl,--start-group libbab.a libaba.a -Wl,--end-group -Wl,--trace
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libaba.a)ab.o
(libaba.a)a.o
(libbab.a)b.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
他们是:
main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libaba.a)ab.o
(libaba.a)a.o
(libbab.a)b.o
这正是程序中需要的。
与--whole-archive ... --no-whole-archive:
$ gcc -o prog2 main2.o libabba.a -Wl,--whole-archive libbab.a libaba.a -Wl,--no-whole-archive -Wl,-trace
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libbab.a)b.o
(libbab.a)x.o
(libaba.a)ab.o
(libaba.a)a.o
(libaba.a)y.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
他们是:
main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libbab.a)b.o
(libbab.a)x.o
(libaba.a)ab.o
(libaba.a)a.o
(libaba.a)y.o
和之前一样,加:
(libbab.a)x.o
(libaba.a)y.o
哪些是死代码(可能还有更多,没有限制)。冗余函数x()和y()在图中定义:
$ nm prog2 | egrep 'T (x|y)'
000000000000067a T x
00000000000006ac T y
外卖
- 如果可以,请使用默认链接,将输入按依赖顺序排列。
- 使用
--start-group ... --end-group 克服循环依赖
库,当您无法修复库时。请注意,联动速度会受到影响。
- 仅当您确实需要链接时才使用
--whole-archive ... --no-whole-archive
... 中所有静态库中的所有目标文件。否则,请执行以下操作之一
前两件事。
[1] 请注意,当调用链接器时,
链接器的命令行
通过 GCC,实际上比您明确传递的链接选项长得多
GCC 命令行,附有样板选项。所以
始终关闭
--whole-archive 和
--no-whole-archive,并关闭
--start-group
与
--end-group。