linker - ld中的--start-group和--whole-archive有什么区别
问题描述
老实说,我认为这个问题盯着看应该很容易man ld
。但是,通过阅读手册页并阅读其他人完成的代码,我发现人们可以互换使用它们,或者在他们认为传递给链接器的库的顺序可能存在问题时同时使用它们。
我想知道这两个选项之间有什么区别以及使用它们时的最佳实践是什么。
谢谢!
相关链接:
解决方案
在撰写本文时,静态库上的 Stackoverflow 标签 wiki 告诉我们:
静态库是目标文件的存档。用作链接器输入,链接器提取它需要进行链接的目标文件。
所需的目标文件是那些为链接器提供符号定义的目标文件,它发现在其他输入文件中使用的符号没有定义。所需的目标文件,而不是其他文件,从存档中提取并输入到链接中,就像它们是链接命令中的单独输入文件一样,并且根本没有提到静态库。
...
链接器通常会支持一个选项(GNU ld:--whole-archive,MS 链接:/WHOLEARCHIVE)来覆盖静态库的默认处理,而是链接所有包含的目标文件,无论它们是否需要。
除了从中提取的目标文件之外,静态库对链接没有任何贡献,这些文件可能在不同的链接中有所不同。它将与共享库进行对比,共享库是另一种在链接中扮演完全不同角色的文件。
这应该清楚地说明了什么--whole-archive
。的范围--whole-archive
一直持续到链接器命令行结束或直到--no-whole-archive
出现1。
默认情况下,链接器将仅在链接器输入的命令行序列中出现该库的每个点检查静态库。为了解析它在以后的输入中发现的符号引用,它不会向后重新检查静态库。
这--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
,因为后者将链接所有必要的目标文件和所有不必要的目标文件,而无需费心区分。
但反之则不然。这是一个简单的例子:
xc
#include <stdio.h>
void x(void)
{
puts(__func__);
}
yc
#include <stdio.h>
void y(void)
{
puts(__func__);
}
主程序
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
那失败了,因为对x
in的引用main.o
只是被链接器发现太晚了,无法找到x
in的定义libxy.a(x.o)
。libxy.a
它首先到达并没有找到它需要的目标文件。那时,它还没有将任何目标文件链接到程序中,因此它需要解析 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__);
}
公元前
#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
推荐阅读
- sql - 如何更有效地散列字符串?md5 与 sha256
- swift - Swift - 按下按钮时不播放音频。为什么?
- haskell - 与gameworld一起在main函数中启动Euterpea Music?(在哈斯克尔)
- indexing - 使用空间填充曲线的空间和时空索引
- matlab - 采样频率与低通的最佳比率
- ios - 无密码电子邮件身份验证
- python - 如何在模型的 post_save 上设置 ManyToMany 字段?
- javascript - 异步 javascript,承诺链接 - 解析后函数未执行
- python - 如何在for循环Python中拆分数组
- excel - 使用 VBA 的形状标签的透明背景