下面是一个非常简单的链接器行为说明,它令人费解
您:
主要的c
extern void foo(void);
int main(void)
{
foo();
return 0;
}
富。c
#include <stdio.h>
void foo(void)
{
puts(__func__);
}
酒吧c
#include <stdio.h>
extern void do_bar(void);
void bar(void)
{
do_bar();
}
do\u栏。c
#include <stdio.h>
void do_bar(void)
{
puts(__func__);
}
让我们将所有这些源文件编译为目标文件:
$ gcc -Wall -c main.c foo.c bar.c do_bar.c
现在,我们将尝试链接一个程序,如下所示:
$ gcc -o prog main.o foo.o bar.o
bar.o: In function `bar':
bar.c:(.text+0x5): undefined reference to `do_bar'
未定义的函数
do_bar
仅在定义中引用
属于
bar
和
酒吧
中未引用
完全是程序。那么为什么联动装置会失效呢?
很简单,这个链接失败是因为
我们让链接器链接
bar.o
进入程序
; 的确如此;和
酒吧o
包含的定义
酒吧
,
哪些参考文献
do\u栏
,该链接中未定义。
酒吧
不是
已引用,但
do\u栏
是
-根据
酒吧
,它在程序中链接。
默认情况下,链接器要求链接中引用的任何符号
链接中定义了程序的。如果我们强迫它把定义联系起来
属于
酒吧
,则需要定义
do\u栏
,因为没有
定义
do\u栏
实际上没有
得到了
定义
酒吧
. 如果链接
定义
酒吧
,这并不怀疑我们
需要
要链接它,
然后允许未定义的引用
do\u栏
如果答案是否定的。
悬挂机构故障当然可以通过以下方式修复:
$ gcc -o prog main.o foo.o bar.o do_bar.o
$ ./prog
foo
现在在本图中,链接
酒吧o
在这个节目中完全是免费的。我们
也可以通过以下方式成功链接
不
告诉链接器链接
酒吧o
.
gcc -o prog main.o foo.o
$ ./prog
foo
酒吧o
和
do_bar.o
都是多余的
正在执行
main
,但该程序只能与两者链接,或两者都不能链接
但是假设
foo
和
酒吧
是否在同一文件中定义?
它们可能在同一个对象文件中定义,
foobar.o
:
ld -r -o foobar.o foo.o bar.o
然后:
$ gcc -o prog main.o foobar.o
foobar.o: In function `bar':
(.text+0x18): undefined reference to `do_bar'
collect2: error: ld returned 1 exit status
现在,链接器无法链接
foo公司
不同时链接
定义
酒吧
. 所以再一次,我们必须将
do\u栏
:
$ gcc -o prog main.o foobar.o do_bar.o
$ ./prog
foo
像这样连在一起,
prog
包含的定义
foo公司
,
酒吧
和
do\u栏
:
$ nm prog | grep -e foo -e bar
000000000000065d T bar
0000000000000669 T do_bar
000000000000064a T foo
(
T
=定义的函数符号)。
同样地,
foo公司
和
酒吧
可能在同一共享库中定义:
$ gcc -Wall -fPIC -c foo.c bar.c
$ gcc -shared -o libfoobar.so foo.o bar.o
然后这个链接:
$ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd)
./libfoobar.so: undefined reference to `do_bar'
collect2: error: ld returned 1 exit status
与之前一样失败,并且可以通过相同的方式修复:
$ gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd)
$ ./prog
foo
当我们链接共享库时
libfoobar.so
而不是对象
文件
福巴。o
我们的
掠夺
具有不同的符号表:
$ nm prog | grep -e foo -e bar
00000000000007aa T do_bar
U foo
这次,
掠夺
不包含以下各项的定义
foo公司
或
酒吧
. 它
包含未定义的引用(
U
)至
foo公司
,因为它调用
foo公司
,和
现在,在运行时,该引用将通过中的定义得到满足
libfoobar。所以
.
甚至没有未定义的引用
酒吧
,也不应该有,因为该计划
从不打电话
酒吧
.
但尽管如此,
掠夺
包含
定义
属于
do\u栏
,现在未被引用
来自符号表中的所有函数。
这与您自己的SSCCE相呼应,但不太复杂。在您的情况下:
-
对象文件
libsub.a(shared2.o)
是
链接到程序中,以提供
func2a
和
func2b
.
-
必须找到并链接这些定义,因为它们分别在的定义中引用
Client_func2a
和
Client_func2b
,定义于
libcshared.so
.
-
libc共享。所以
必须链接以提供
Client_func1a
.
-
定义
客户端功能1a
必须找到并链接,因为
引用自的定义
func1a
.
-
和
功能1A
由调用
主要的
.
这就是为什么我们看到:
$ nm main | grep func2
U Client_func2a
U Client_func2b
00000000004009f7 T func2a
0000000000400a30 T func2b
在程序的符号表中。
将定义链接到
它不调用的函数。它通常以我们所看到的方式发生:联系,
递归解析以开头的符号引用
主要的
,发现它需要定义
属于
f
,它只能通过链接某些对象文件来获取
file.o
,和
文件o
它还链接了函数的定义
g
,从未调用。
什么
是
很奇怪的是,最终会有一个像
主要的
就像我上一个版本的
掠夺
,
其中包含未调用函数的定义(例如
do\u栏
)链接到解决的
来自另一个未调用函数定义的引用(例如。
酒吧
)那就是
不
在程序中定义。
即使有冗余的函数定义,通常我们也可以将它们链接回一个或多个
链接中的对象文件
第一
冗余定义与
一些必要的定义。
这种奇怪的现象是由以下情况引起的:
gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd)
因为必须链接的第一个冗余函数定义(
酒吧
)是的
通过链接提供
共享库
,
libfoobar。所以
,而
do\u栏
这是
酒吧
是
不
在该共享库或任何其他共享库中,
但是在
对象文件
.
定义
酒吧
由提供
libfoobar。所以
当
程序与该共享库链接。它不会与
程序这就是动态链接的本质。但是
链接-是否是独立的对象文件,如
do\u栏。o
或一个
链接器从存档中提取的
libsub。a(共享2.o)
-只能是
物理链接到程序中。所以多余的
do\u栏
出现在
符号表
掠夺
. 但是多余的
酒吧
,这就解释了为什么
do\u栏
有,
不是的
那里在的符号表中
libfoobar。所以
.
当您在程序中发现死代码时,您可能希望链接器更智能。
通常
可以
更聪明一些,但要付出一些额外的努力。你需要让它
垃圾收集区
,
在此之前,您需要要求编译器通过生成
数据部分
和
功能部分
在对象文件中。看见
How to remove unused C/C++ symbols with GCC and ld?
和
the answer
但这种修剪死代码的方法在异常情况下不起作用
死代码在程序中链接,以满足来自
共享库
悬挂机构所需。链接器只能从中递归垃圾收集未使用的节
那些
输出
输入到程序中,它只输出输入的部分
来自对象文件,而不是来自要动态链接的共享库。
避免您的
主要的
还有我的
掠夺
是
不要做那种奇怪的联系
在哪儿
共享库将包含程序不调用但必须调用的未定义引用
通过将死目标代码链接到
程序
.
相反,在构建共享库时,要么不要在其中留下任何未定义的引用,
或者只留下未定义的引用,这些引用应通过其自身的动态依赖性来满足。
所以,建立我的
libfoobar。所以
是:
$ gcc -shared -o libfoobar.so foo.o bar.o do_bar.o
这为我提供了一个共享库,其API为:
void foo(void);
void bar(void);
对于想要其中一个或两个的人,没有未定义的引用。然后
我构建的程序是
foo公司
:
$ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd)
$ ./prog
foo
而且它不包含死代码:
$ nm prog | grep -e foo -e bar
U foo
同样,如果您构建
libshared.so
没有未定义的引用,例如:
$ gcc -c -fPIC shared2.c shared1.c
$ ar -crs libsub.a shared1.o shared2.o
$ gcc -shared -o libcshared.so cshared1.o cshared2.o -L. -lsub
然后链接您的程序:
$ gcc -o main main.c libcmain.so libcshared.so
它也不会有死代码:
$ nm main | grep func
U func1a
如果你不喜欢
libsub.a(shared1.o)
和
libsub。a(共享2.o)
物理链接到
libc共享。所以
通过此解决方案,然后
另外
链接共享库的传统方法:保留所有
func*
中未定义的函数
libc共享。所以
:制造
libsub
而且
一个共享库,它是
libc共享。所以
.