在C程序中,如果在两个具有不同实现的不同库中定义了相同功能,会发生什么情况?

时间:2019-01-05 07:46:07

标签: c static-libraries

如果我们有一个函数foo()在具有不同实现的两个不同库中具有相同的原型,并且如果我们仅包含单个头文件(具有函数的声明),那么如果尝试在以下位置编译或执行程序,会发生什么情况?编译还是运行时?

2 个答案:

答案 0 :(得分:4)

发生的事情是特定于实现的(甚至在C11标准n1570中都没有指定,但是我留给您检查一下;该标准的AFAIK并没有提到{{3 }}-仅libraries)。 我相信您提供的场景是translation units,因此您可能会undefined behavior(因为允许任何事情发生)。实现已定义。

实际上,在我的Linux系统上,我会在scared时遇到一些错误(至少对于静态库而言)。阅读linkProgram Library HowTo。请注意,documentation of the ld linker编译器(有时)正在运行ld(有许多其他选项,通常对您而言是隐藏的)。因此,GCC gccinvoke(以了解ld命令如何启动gcc)。

如果在Linux上链接两个共享库,它将变得更加有趣。但是请阅读Drepper的-v flag。 IIRC,第一个符号定义将覆盖第二个。例如,您可以使用How to Write Shared Libraries并将-ljemalloc链接到隐式-lc之前(两个 库都定义一个malloc符号)。还应注意jemallocplugins(在Linux上,请参阅dynamic linkerdlopen(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__);
}

功能foop.cq.c中定义。

将所有.c文件编译为.o文件:

$ gcc -c main.c p.c q.c r.c

创建三个静态库,每个p.oq.or.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

仅包含 foop.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

作为程序,它仅包含fooq.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)定义了foobar。所以我们可以链接 我们的程序如下:

$ 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

这次,没有错误。 foolibx.a(p.o)的定义是 链接。 libyz.a(qr.o)中的弱定义将被忽略。该程序 像这样运行:

$ ./prog
foo from p.c
bar from qr.c

如果符号定义不弱,则为 strong 。链接器的规则是:

  • 最多可以链接一个符号的强定义。
  • 如果输入了一个或多个弱定义以及强定义, 那么一个强定义将被链接,而所有弱定义将被忽略。
  • 如果仅输入弱定义,则链接器可以选择其中任何一个 任意地。 (实际上,它会选择首先找到的那个)。

请勿仅使用弱符号定义来逃避多个定义 令您惊讶的错误。这样的惊喜意味着你不明白 您的联系。分析并修复它,以便您不再试图 包含同一事物的多个定义的程序。

弱符号定义通常由编译器生成,位于 场景,以实现需要它们的源语言功能(例如 全局内联函数定义或C ++中的模板实例化)。 仅当您完全了解您为什么想要链接器时,才使用它们 输入相同符号的多个定义。