今天,在使用一个自定义库时,我发现了一个奇怪的行为。静态库代码包含调试main()
函数。它不在#define
标志内。所以它也出现在图书馆里。它被用来链接到另一个包含真实CCD_ 3的程序
当两者链接在一起时,链接器不会为main()
抛出多个声明错误。我想知道这怎么会发生。
为了简单起见,我创建了一个模拟相同行为的示例程序:
$ cat prog.c
#include <stdio.h>
int main()
{
printf("Main in prog.cn");
}
$ cat static.c
#include <stdio.h>
int main()
{
printf("Main in static.cn");
}
$ gcc -c static.c
$ ar rcs libstatic.a static.o
$ gcc prog.c -L. -lstatic -o 2main
$ gcc -L. -lstatic -o 1main
$ ./2main
Main in prog.c
$ ./1main
Main in static.c
"2main"二进制文件如何找到要执行的main
?
但将两者同时编译会产生一个多重声明错误:
$ gcc prog.c static.o
static.o: In function `main':
static.c:(.text+0x0): multiple definition of `main'
/tmp/ccrFqgkh.o:prog.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status
有人能解释一下这种行为吗?
报价ld(1):
链接器只会在命令行中指定的位置搜索存档一次。如果存档定义了一个在命令行上出现在存档之前的某个对象中未定义的符号,则链接器将包括存档中的相应文件。
当链接2main时,主符号在ld到达-lstatic之前得到解析,因为ld是从prog.o.中提取的
当链接1main时,当它到达-lstatic时,您确实有未定义的main,所以它会在存档中搜索main。
此逻辑仅适用于存档(静态库),而不适用于常规对象。当您链接prog.o和static.o时,来自这两个对象的所有符号都会被无条件地包括在内,因此您会得到一个重复的定义错误。
当链接静态库(.a)时,链接器仅在迄今为止跟踪到任何未定义符号的情况下搜索存档。否则,它根本不会查看存档。因此,在2main
的情况下,它从不查看存档,因为它没有任何未定义的符号来制作翻译单元。
如果在static.c
:中包含一个简单的函数
#include <stdio.h>
void fun()
{
printf("This is funn");
}
int main()
{
printf("Main in static.cn");
}
并从prog.c
调用它,则链接器将被迫查看存档以找到符号fun
,并且您将得到与链接器现在找到重复符号main
相同的多重主定义错误。
当您直接编译对象文件时(如在gcc a.o b.o
中),链接器在这里没有任何作用,所有的符号都包括在内,以形成一个二进制文件,并且明显存在重复的符号。
最重要的是,只有在缺少符号的情况下,链接器才会查看存档。否则,它就像不与任何库链接一样好。
链接器加载任何对象文件后,会在库中搜索未定义的符号。如果没有,那么就不需要读取库。既然已经定义了main,即使它在每个库中都找到了一个main,也没有理由加载第二个main。
然而,链接者有着截然不同的行为。例如,如果您的库包含一个包含main()和foo()的对象文件,并且foo未定义,那么您很可能会得到一个多重定义符号main()的错误。
现代(同义重复)链接器会从不可访问的对象(例如AIX)中省略全局符号。Solaris和Linux系统上的老式链接器仍然像20世纪70年代的unix链接器一样,从对象模块加载所有符号,无论是否可访问。这可能是可怕的膨胀以及过多的链接时间的来源。
*nix链接器的另一个特点是,每次列出库时,它们只有效地搜索一次库。这就要求程序员除了编写程序外,还要在链接器的命令行或make文件中订购库。不需要图书馆的有序列表是不现代的。较旧的操作系统通常具有链接器,该链接器会重复搜索所有库,直到传递未能解析符号为止。