如果我们有一个函数foo()在具有不同实现的两个不同库中具有相同的原型,并且如果我们仅包含单个头文件(具有函数的声明),那么如果尝试在以下位置编译或执行程序,会发生什么情况?编译还是运行时?
答案 0 :(得分:4)
发生的事情是特定于实现的(甚至在C11标准n1570中都没有指定,但是我留给您检查一下;该标准的AFAIK并没有提到{{3 }}-仅libraries)。 我相信您提供的场景是translation units,因此您可能会undefined behavior(因为允许任何事情发生)。实现已定义。
实际上,在我的Linux系统上,我会在scared时遇到一些错误(至少对于静态库而言)。阅读link和Program Library HowTo。请注意,documentation of the ld
linker编译器(有时)正在运行ld
(有许多其他选项,通常对您而言是隐藏的)。因此,GCC gcc
与invoke(以了解ld
命令如何启动gcc
)。
如果在Linux上链接两个共享库,它将变得更加有趣。但是请阅读Drepper的-v
flag。 IIRC,第一个符号定义将覆盖第二个。例如,您可以使用How to Write Shared Libraries并将-ljemalloc
链接到隐式-lc
之前(两个 库都定义一个malloc
符号)。还应注意jemalloc和plugins(在Linux上,请参阅dynamic linker和dlopen(3)和dlsym(3))。
Linux也具有弱符号,请参见ld-linux.so(8),其this具有GCC属性。
在Windows(我不知道且从未使用过)上,情况可能有所不同。但是,您仍然可以在链接时出现一些“乘法定义的符号”错误。
visibility以特定于操作系统的方式工作。因此,请阅读Levine的linker(它解释了Windows和Unix链接器如何工作,并且细节有所不同)。要了解有关操作系统的更多信息,请阅读Linkers and Loaders(可免费下载)。
答案 1 :(得分:4)
每个标签都在询问静态库的链接。链接器 不知道或不在乎其输入文件使用了什么源语言 从。 C语言对这个问题无关紧要,但是我将使用C进行说明。
阅读Stackoverflow tag Wiki about static libraries,它将说明该链接 您的带有静态库的程序与链接程序完全相同 0个或多个静态库中存储的目标文件-即 链接器需要提供0个或多个目标文件的定义,否则 程序中未解析的符号引用。
链接器一旦在提供它的静态库p.o
中找到了目标文件libx.a
文件,
带有程序引用的符号foo
的定义,它将链接
将目标文件libx.a(p.o)
插入程序以解析foo
。它不会
尝试在其他任何目标文件foo
中找到其他q.o
的定义
链接中liby.a
之后的静态库libx.a
。
因此,如果在任何其他静态库q.o
中有 个其他目标文件liby.a
在链接中比libx.a
晚的链接中还包含以下内容的定义:
foo
,该目标文件liby.a(q.o)
甚至都不会链接到程序中
除非,链接器需要它提供一些 other 符号bar
的定义
该程序所指的。假设情况并非如此,liby.a(q.o)
可能
出于链接目的,也不存在。
链接器不会链接libx.a(p.o)
和liby.a(q.o)
中相同符号的多个定义
不需要。它将链接定义libx.a(p.o)
的 first 对象文件foo
。
进入程序,然后通过定义foo
来完成。
这是一个例子:
main.c
extern void foo(void);
extern void bar(void);
int main(void)
{
foo();
bar();
return 0;
}
p.c
#include <stdio.h>
void foo(void)
{
printf("%s %s %s\n",__func__,"from",__FILE__);
}
q.c
#include <stdio.h>
void foo(void)
{
printf("%s %s %s\n",__func__,"from",__FILE__);
}
r.c
#include <stdio.h>
void bar(void)
{
printf("%s %s %s\n",__func__,"from",__FILE__);
}
功能foo
在p.c
和q.c
中定义。
将所有.c
文件编译为.o
文件:
$ gcc -c main.c p.c q.c r.c
创建三个静态库,每个p.o
,q.o
,r.o
中的一个:
$ ar rcs libx.a p.o
$ ar rcs liby.a q.o
$ ar rcs libz.a r.o
然后链接程序,在libx.a
之前输入liby.a
:
$ gcc -o prog main.o libz.a libx.a liby.a -Wl,-trace-symbol=foo
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: libx.a(p.o): definition of foo
诊断链接选项-Wl,-trace-symbol=foo
要求链接器告诉
我们链接到prog
的文件的名称,它在其中找到对foo
的未解析引用,并且
也是定义foo
的文件的名称。您会看到foo
中引用了main.o
并且libx.a(p.o)
提供的定义已链接。 foo
中的另一个定义
liby.a(q.o)
未链接。此链接与完全相同
gcc -o prog main.o r.o p.o
仅包含 foo
中p.o
的定义,作为程序
显示:
$ ./prog
foo from p.c
bar from r.c
现在重新链接prog
,这次是在liby.a
之前与libx.a
进行链接:
$ gcc -o prog main.o libz.a liby.a libx.a -Wl,-trace-symbol=foo
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: liby.a(q.o): definition of foo
这次,foo
的定义与liby.a(q.o)
链接。此链接与以下内容完全相同:
gcc -o prog main.o r.o q.o
作为程序,它仅包含foo
中q.o
的定义
显示:
$ ./prog
foo from q.c
bar from r.c
链接器不在乎您在其中提供的foo
的多少定义
不同静态库中的不同目标文件。它只在乎
如果程序中引用了foo
,那么foo
就被定义了一次,
链接到程序中的文件。
如果您强制链接器将文件链接到包含更多内容的程序
而不是foo
的一个定义,那么默认情况下,链接器通常会
给您一个多个定义错误,链接将会失败,因为
一个程序中对foo
的定义不能超过一个。这是一个
的说明:
qr.c
#include <stdio.h>
void foo(void)
{
printf("%s %s %s\n",__func__,"from",__FILE__);
}
void bar(void)
{
printf("%s %s %s\n",__func__,"from",__FILE__);
}
编译该文件:
$ gcc -c qr.c
在新的静态库中归档qr.o
:
$ ar rcs libyz.a qr.o
目标文件libyz.a(qr.o)
定义了foo
和bar
。所以我们可以链接
我们的程序如下:
$ gcc -o prog main.o libyz.a -Wl,-trace-symbol=foo,-trace-symbol=bar
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: main.o: reference to bar
/usr/bin/ld: libyz.a(qr.o): definition of foo
/usr/bin/ld: libyz.a(qr.o): definition of bar
它的运行方式是:
$ ./prog
foo from qr.c
bar from qr.c
但是,如果我们尝试将其链接为:
$ gcc -o prog main.o libx.a libyz.a -Wl,-trace-symbol=foo,-trace-symbol=bar
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: main.o: reference to bar
/usr/bin/ld: libx.a(p.o): definition of foo
/usr/bin/ld: libyz.a(qr.o): in function `foo':
qr.c:(.text+0x0): multiple definition of `foo'; libx.a(p.o):p.c:(.text+0x0): first defined here
/usr/bin/ld: libyz.a(qr.o): definition of bar
collect2: error: ld returned 1 exit status
foo
有一个多重定义。那是因为:
foo
并在libx.a(p.o)
中找到第一个;
因此它将该文件链接到程序中。它不会搜索其他任何一个。bar
并在libyz.a(qr.o)
中找到第一个;
因此它将该文件链接到程序中。它不会搜索其他任何一个。libyz.a(qr.o)
包含foo
的另一个定义以及一个定义
bar
中的。因此,现在foo
的两个定义已链接在一起,这是一个错误。我说过,如果您将 链接程序尝试将多个定义符号的文件链接到程序中。
但是如果链接器知道这个概念(就像GNU和Apple链接器一样),则可以通过告诉链接器符号weakly defined来避免这种情况。
GCC编译器支持非标准语言扩展__attribute__
syntax
您可以用来与链接器通信符号定义为 weak 。
这是一个说明:
qr.c(2)
#include <stdio.h>
void __attribute__((weak)) foo(void)
{
printf("%s %s %s\n",__func__,"from",__FILE__);
}
void bar(void)
{
printf("%s %s %s\n",__func__,"from",__FILE__);
}
重新编译:
$ gcc -c qr.c
删除libyz.a
并重新创建它:
$ rm libyz.a
$ ar rcs libyz.a qr.o
重试刚刚失败的链接:
$ gcc -o prog main.o libx.a libyz.a -Wl,-trace-symbol=foo,-trace-symbol=bar
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: main.o: reference to bar
/usr/bin/ld: libx.a(p.o): definition of foo
/usr/bin/ld: libyz.a(qr.o): definition of bar
这次,没有错误。 foo
中libx.a(p.o)
的定义是
链接。 libyz.a(qr.o)
中的弱定义将被忽略。该程序
像这样运行:
$ ./prog
foo from p.c
bar from qr.c
如果符号定义不弱,则为 strong 。链接器的规则是:
请勿仅使用弱符号定义来逃避多个定义 令您惊讶的错误。这样的惊喜意味着你不明白 您的联系。分析并修复它,以便您不再试图 包含同一事物的多个定义的程序。
弱符号定义通常由编译器生成,位于 场景,以实现需要它们的源语言功能(例如 全局内联函数定义或C ++中的模板实例化)。 仅当您完全了解您为什么想要链接器时,才使用它们 输入相同符号的多个定义。