man ld
,这个问题应该很容易。然而,通过阅读 manpage 并阅读其他人编写的代码,我发现当人们认为库的顺序可能存在问题时,他们会交替使用它们或同时使用它们。我想知道这两个选项之间的区别是什么,以及在使用它们时的最佳实践是什么。
谢谢!
相关链接:
man ld
,这个问题应该很容易。然而,通过阅读 manpage 并阅读其他人编写的代码,我发现当人们认为库的顺序可能存在问题时,他们会交替使用它们或同时使用它们。这应该可以说明静态库是一组目标文件的归档文件。作为链接器输入时,链接器会提取它需要进行链接的目标文件。
所需的目标文件是那些在其他输入文件中被使用但没有定义的符号提供给链接器的目标文件。 从归档文件中提取所需的目标文件,并将它们与单独的输入文件一样精确地作为链接命令中的输入文件输入到链接中。
……
链接器通常支持一个选项(GNU ld:--whole-archive,MS link:/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
成功,
因为后者将连接所有必要的对象文件和所有不必要的对象文件,而无需区分它们。#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
$ 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
来使其成功。
a.c
#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)
引用了在libaba.a(ab.o)
中定义的ab
;
它还引用了在libbab.a(ba.o)
中定义的ba
。
因此,在链接顺序中,libabba.a
必须出现在libbab.a
和libaba.a
之前。
libbab.a
依赖于libaba.a
。具体而言,libbab.a(ba.o)
引用了在libaba(a.o)
中定义的a
。
但是,libaba.a
也依赖于libbab.a
。libaba(ab.o)
引用了在libbab(b.o)
中定义的b
。
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
。否则,请执行前两个操作中的任何一个。--whole-archive
后面加上--no-whole-archive
,并使用--start-group
后面加上--end-group
。
--whole-archive
将整个库放入链接文件中,即使是不需要的部分也会被包含进去。使用组不会将整个库放入其中,而是一直解析符号,直到在该组中找不到新的未解析符号为止。 - geza