C/C++代码中函数的多重定义问题

7
这是一个关于C/C++函数定义的问题。讨论的代码是提供Rmath.h头文件中定义的静态libRmath
该库提供的文档指出,用户可以选择是否提供函数double unif_rand(void)的定义。
那么我的问题是,如果这样的函数定义是可选的,那么在C/C++中不允许有多个函数定义的问题会不会存在?

编辑: 虽然不看源代码就猜测事物是如何工作可能很诱人,但这并不是我想要的。我对了解它的真正工作方式感兴趣,因此你可能需要阅读源代码文档来回答这个问题。


我的猜测是函数def在#ifdef中,这样如果你定义了它,那么你的函数会被链接器首先找到,因此已经存在的函数将被忽略。 - baash05
2个回答

15
当您的应用程序被链接时,未解析的符号将使用您提供的库进行解析。如果您没有定义一个函数,在链接期间它将成为未解析的符号,因此链接器将尝试使用librmath来解析该符号,在这种情况下。如果一个或多个符号无法解析,您将收到链接器错误。

但是,如果您在代码中定义了该函数,它将在链接期间已经定义,因此不需要使用外部库的符号来解析它。

您不能在应用程序中定义相同的符号超过一次。

编辑:由于另一个答案中有很多争论,我做了一个实际的例子。我创建了一个共享对象(类似于Windows的DLL),其中定义并导出了一个名为foo的函数:

//lib.h
extern "C" {
    void foo();
    void bar();
};

//lib.cpp
#include <iostream>
#include "lib.h"

void foo() {
    std::cout << "From lib\n";
}

void bar() {
    std::cout << "Bar, calling foo\n";
    foo();
}
为了测试这个共享对象,我创建了一个与它链接的应用程序:
//test.cpp
#include <iostream>
#include "lib.h"

void foo() {
    std::cout << "From app\n";
}

int main() {
    bar();
}

我已经编译了共享对象和应用程序:

g++ lib.cpp -o libtest.so -Wall -fPIC -shared -Wl,--export-dynamic -Wl,-soname,libtest.so -Wl,-z,defs
g++ test.cpp -o test -L. -ltest

当我执行test,将库路径设置为".",以便可以加载我的共享对象时,我会得到如下输出:

matias@master:/tmp$ LD_LIBRARY_PATH="." ./test
Bar, calling foo
From app

正如您所看到的,应用程序中定义的foo函数被调用(而不是共享对象)。您基本上可以为共享对象中的每个导出符号执行此操作。

编辑2:我已在lib.h中添加了另一个导出的函数。 现在应用程序调用此函数,该函数最终调用foo。 结果与预期相同。

编辑3:好的,让我们深入探讨。 这是bar函数的转储:

Dump of assembler code for function bar@plt:
   0x0804855c <+0>: jmp    DWORD PTR ds:0x804a004
   0x08048562 <+6>: push   0x8
   0x08048567 <+11>:    jmp    0x804853c

如果我们访问地址0x804a004

Dump of assembler code for function _GLOBAL_OFFSET_TABLE_:
   0x08049ff4 <+0>: or     BYTE PTR [edi+0x804],bl
   0x08049ffa <+6>: add    BYTE PTR [eax],al
   0x08049ffc <+8>: add    BYTE PTR [eax],al
   0x08049ffe <+10>:    add    BYTE PTR [eax],al
   .....

正如你所看到的,它正在跳转到全局偏移表。你可以在这里这里了解有关GOT的信息。动态符号(在运行时解析的符号)存储在该表中。每当调用应在运行时解析的符号时,实际上会跳转到此表,然后跳转到存储在该表相应条目中的地址。由于应用程序定义了foo,因此GOT包含来自test.cpp中定义的地址,而不是我们共享对象中的地址。

EDIT4:好吧,最后一次编辑。引用文档:

您将需要提供均匀随机数生成器。

 double unif_rand(void)

文档明确指出,如果您正在使用动态库,您不能提供自己的unif_rand实现。因此,我相信我所指出的实际上回答了您的问题。


这就是为什么我说你回答中的其余部分都是细节。同时,我简短地说了一下:现在一切都取决于你如何将它们链接到你的程序上。 在Windows系统中,有几种方法可以实现。 - Nawaz
1
但是你有注意到应用程序中的 foo 被调用了吗?而不是来自 DLL 的那个?顺便说一下,我很惊讶看到这个;我认为你做错了什么。无论如何,你会如何从 DLL 中调用 foo - Nawaz
1
文档中说,使用 DLL 时您不能提供自己的 DLL(“使用...DLL 时必须使用提供的 DLL”)。我不熟悉共享对象,但是了解 Win DLL 的工作原理后,这听起来是正确的。在链接 DLL 时,DLL 函数调用不会改变,而是使用 DLL 的程序中的调用会发生改变。 - Suma
@fontanini接受了你的答案。看起来,如果库是静态的,这也可以工作,那么你在示例中使用动态库有特殊原因吗? - ggg
1
ggg,你的问题一开始没有提到静态库,所以我采用了动态库的方法,因为我知道后者使用时会出现这种情况。@Suma 谢谢,我理解错了。感谢你的编辑:D。 - mfontanini
显示剩余9条评论

0

链接静态库与链接静态库中的所有对象略有不同。

静态库中的定义仅在需要时被引入,因此它们不会导致多重定义错误。

这产生了一些副作用,例如 全局 初始化器 在静态库内运行,但主程序中没有任何东西引用该对象时不会运行的 众所周知问题


一个小建议;可能值得解释一下“例如,在静态库中全局初始化器不运行,当主程序中没有引用该对象时的众所周知的问题”,因为这听起来不像是个问题,而是正确的;“对象未使用,因此不存在初始化开销”听起来比“即使对象未使用,仍存在初始化开销”更好。是否已经有Stack Overflow问题可以作为参考?只是一个想法。 - gbulmer
你能用一些C++代码和编译命令详细说明吗? - ggg
@gbulmer:http://stackoverflow.com/questions/6317796/ctor-init-not-calling-the-global-ctor-instances-in-library 和 https://dev59.com/Jmox5IYBdhLWcg3wDgTR 以及 https://dev59.com/zFfUa4cB1Zd3GeqPL9tU - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接