【问题标题】:Multiple definition of a function in C/C++ codeC/C++代码中函数的多重定义
【发布时间】:2012-03-27 01:14:04
【问题描述】:

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

为库提供的documentation 声明用户可选择为函数double unif_rand(void) 提供函数定义。

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

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

【问题讨论】:

  • 我的猜测是函数 def 在 #ifdef 中,因此如果您定义它,那么链接器首先会找到您的函数,因此已经存在的函数将被忽略。

标签: c++ c r


【解决方案1】:

当您的应用程序被链接时,未解析的符号将使用您提供的库进行解析。如果您没有定义函数,它将在链接期间成为未解析的符号,因此,在这种情况下,链接将尝试使用 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 函数被调用。您基本上可以对共享对象中的每个导出符号执行此操作。

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 herehere 的信息。动态符号(在运行时解析)存储在此表中。每当您调用应该在运行时解析的符号时,您实际上是跳转到该表,然后跳转到存储在该表对应条目中的地址。由于应用程序定义了foo,GOT 包含来自test.cpp 的定义地址,而不是我们共享对象中的地址。

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

您需要提供统一的随机数生成器

 double unif_rand(void)

或使用提供的(使用动态库或 DLL,您将 必须使用提供的(...)

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

【讨论】:

  • 这基本上和我说的一样。
  • 最后一段基本相同。其余的不是。
  • 但是知道通知foo 来自应用程序被调用了吗?不是来自 DLL 的那个?顺便说一句,我很惊讶。我认为你做错了什么。无论如何,你会如何从 DLL 中调用foo
  • 文档说使用 DLL 你不能提供你自己的(“使用 ... DLL 你将不得不使用提供的那个”)。我不熟悉共享对象,但知道 Win DLL 是如何工作的,这听起来是正确的。链接 DLL 时,DLL 函数调用不会改变,改变的是使用 DLL 的程序的调用。
  • ggg,你的问题一开始没有提到静态库,我选择了动态库方法,因为我知道在使用后者时会发生这种情况。 @Suma 谢谢,我错了。感谢您的编辑:D。
【解决方案2】:

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

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

这有一些副作用,例如 the well-known problem 静态库中的全局初始化程序在主程序中没有任何内容引用该对象时不会运行。

【讨论】:

  • 一个小评论;可能值得解释“例如,当主程序中没有任何内容引用该对象时,静态库中的全局初始化程序不运行的众所周知的问题”,因为这听起来不像是一个问题,但听起来是正确的; “未使用对象,因此没有初始化开销”听起来比“即使未使用对象,仍然存在初始化开销”更好。是否已经有可以用作参考的 stackoverflow 问题?只是一个想法。
  • 你能详细说明一些C++代码和编译命令吗?
猜你喜欢
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-31
  • 1970-01-01
  • 2011-06-19
  • 1970-01-01
相关资源
最近更新 更多