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

Multiple definition of a function in C/C++ code

本文关键字:定义 函数 C++ 代码      更新时间:2023-10-16

这是一个关于C/C++函数定义的问题。所讨论的代码是静态libRmath,它在R中的Rmath.h头文件中提供定义。

为库提供的文档指出,用户可以选择为函数double unif_rand(void)提供函数定义。

所以我的问题是,如果这样的函数定义是可选的,那么C/C++中不允许有多个函数定义的问题吗?

编辑:在不看源代码的情况下推测事情是如何工作的可能很诱人,但这不是我想要的。我很想知道是如何真正工作的,所以你可能需要阅读源代码和文档来回答这个问题。

链接应用程序时,将使用您提供的库解析未解析的符号。如果你不定义一个函数,在链接过程中它将是一个未解析的符号,因此,在这种情况下,链接者将尝试使用librmath解析该符号。如果一个或多个符号无法解析,则会出现链接器错误。

但是,如果您确实在代码中定义了函数,那么它将在链接过程中已经定义,因此不需要使用外部库中的符号来解决它。

您不能在应用程序中多次定义同一个符号。

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

//lib.h
extern "C" {
    void foo();
    void bar();
};
//lib.cpp
#include <iostream>
#include "lib.h"
void foo() {
    std::cout << "From libn";
}
void bar() {
    std::cout << "Bar, calling foon";
    foo();
}

为了测试这个共享对象,我创建了一个与之链接的应用程序:

//test.cpp
#include <iostream>
#include "lib.h"
void foo() {
    std::cout << "From appn";
}
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函数(而不是共享对象)。基本上可以对共享对象中的每个导出符号执行此操作。

EDIT2:我在lib.h中添加了另一个导出的函数。应用程序现在调用这个函数,它最终调用foo。结果和预期的一样。

EDIT3:好的,让我们深入了解。这是来自函数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)

或者使用提供的(并且使用动态库或DLL必须使用提供的

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

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

静态库中的定义只有在需要时才会被拉入,因此它们不会导致多个定义错误。

这有一些副作用,例如,当主程序中没有任何内容引用该对象时,静态库中的全局初始化程序不运行这一众所周知的问题。