今天,在使用一个自定义库时,我发现了一种奇怪的行为。
静态库代码包含调试main()
函数。它不在#define
标志内。所以它也存在于库中。它用于链接到另一个包含真实main()
的程序
当它们都链接在一起时,链接器不会为main()
抛出多个声明错误。我想知道这是怎么发生的。
为简单起见,我创建了一个模拟相同行为的示例程序:
$ cat prog.c
#include <stdio.h>
int main()
{
printf("Main in prog.c\n");
}
$ cat static.c
#include <stdio.h>
int main()
{
printf("Main in static.c\n");
}
$ 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
有人可以解释一下这种行为吗?
答案 0 :(得分:57)
引用ld(1):
链接器将仅在命令行上指定的位置搜索一次存档。如果存档定义了在命令行中存档之前出现的某个对象中未定义的符号,则链接器将包含存档中的相应文件。
当链接2main时,主符号在ld到达-lstatic之前被解析,因为ld从prog.o中拾取它。
当链接1main时,在到达-lstatic时你确实有未定义的main,因此它会在归档中搜索main。
此逻辑仅适用于归档(静态库),而不适用于常规对象。 链接prog.o和static.o时,无条件地包含来自两个对象的所有符号,因此您会得到重复的定义错误。
答案 1 :(得分:17)
链接静态库(.a)时,如果到目前为止跟踪了任何未定义的符号,链接器仅搜索存档。否则,它根本不会查看存档。所以你的2main
案例,它从不查看档案,因为它没有任何未定义的符号来制作翻译单元。
如果在static.c
中包含一个简单的函数:
#include <stdio.h>
void fun()
{
printf("This is fun\n");
}
int main()
{
printf("Main in static.c\n");
}
并从prog.c
调用它,然后链接器将被强制查看存档以找到符号fun
,并且您将获得相同的多个主要定义错误,因为链接器会找到重复的符号main
现在。
当您直接编译目标文件时(如gcc a.o b.o
中所述),链接器在此处没有任何作用,并且包含所有符号以生成单个二进制文件,并且显然存在重复符号。
底线是链接器仅在缺少符号时才查看存档。否则,它与不与任何库链接一样好。
答案 2 :(得分:1)
链接器加载任何目标文件后,它会在库中搜索未定义的符号。如果没有,则不需要读取库。由于main已被定义,即使它在每个库中找到一个main,也没有理由加载第二个。
但是,林克斯的行为却截然不同。例如,如果您的库包含一个目标文件,其中包含main()和foo(),并且未定义foo,则很可能会出现多重定义的符号main()的错误。现代(重言式)链接器将省略不可达的对象的全局符号 - 例如AIX。像Solaris上那样的旧式连接器,Linux系统的行为仍然像20世纪70年代的unix连接器,从对象模块加载所有符号,无论是否可达。这可能是一个可怕的膨胀和过多的链接时间的来源。
* nix链接器的特征还在于它们每次列出时都只能有效地搜索一次库。除了编写程序之外,这还要求程序员将命令行上的库命令为链接器或make文件。不需要有序的库列表并不现代。较旧的操作系统通常具有重复搜索所有库的链接器,直到通过无法解析符号。