【问题标题】:Multiple definition of inline functions when linking static libs链接静态库时内联函数的多重定义
【发布时间】:2011-01-14 03:05:11
【问题描述】:

我有一个用 mingw 编译的 C++ 程序(Windows 的 gcc)。使用包含 gcc 4.4.1 的 mingw 的 TDM 版本。可执行文件链接到两个静态库 (.a) 文件:其中一个是用 C 编写的第三方库;另一个是我自己写的 C++ 库,它使用 C 库在上面提供了我自己的 C++ API。

C 库功能的一部分(在我看来是多余的)是在内联函数中实现的。当您使用 C 库的 API 时,您无法避免包含内联函数,但是当我尝试将它们全部链接在一起时,我收到链接错误,说所有内联函数都有多个定义 - 我都有在我的 C++ 包装库和我没有调用的库中调用,基本上在标头中内联定义的任何内容都已在 C 库和 C++ 库中为它创建了一个函数。

在同一个项目的不同.c或.cpp文件中多次使用include文件不会导致多次定义错误;问题在于它会为每个库生成一个定义。

编译器如何/为什么为这两个库中的这些内联函数生成函数和符号?如何强制它停止在我的代码中生成它们?有没有可以运行的工具从 .a 文件中去除重复的函数,或者有一种方法可以让链接器忽略多个定义?

(仅供参考,第三方库确实在其所有标头中都包含 #ifdef __cplusplus 和 extern "C" 保护;无论如何,如果这是问题所在,它不会导致符号的多重定义,它会导致相反的问题,因为该符号将是未定义的或至少是不同的。)

值得注意的是,如果我链接到第三方 C 库的 DLL,则不会出现链接错误;但是,然后我遇到奇怪的运行时故障,这似乎与我的代码有自己的函数版本有关,它应该从 DLL 调用。 (好像编译器正在创建我没有要求的函数的本地版本。)

之前有人问过这个问题的类似版本,但是,我在以下任何一个中都没有找到我的情况的答案:

这个问题的答案是发帖者是多重定义变量,我的问题是内联函数的多重定义:Repeated Multiple Definition Errors from including same header in multiple cpps

这是一个 MSVC 程序,但我使用的是 mingw;另外,发帖人在这个问题中的问题是在标头中的类主体之外定义了一个 C++ 类构造函数,而我的问题是内联的 C 函数:Static Lib Multiple Definition Problem

这个傻瓜将他所有的 C 代码重命名为 C++ 文件,而他的 C 代码不是 C++ 安全的:Multiple definition of lots of std:: functions when linking

这个只是想知道为什么违反一个定义规则不是错误:unpredictable behavior of Inline functions with different definitions

【问题讨论】:

  • 请注意,C99 内联语义不同于 C++:在 C 中,如果显式指定内联函数声明之一extern,它会创建一个外部定义 - 这不是内联定义了。这些外部定义不能在程序中出现多次。在 C++ 中,对此类函数的显式 extern 无效。你最好在 C 中使用static inline
  • 通常内联函数定义被标记为“弱”符号,链接器应该自己删除所有重复项。
  • 感谢 Johannes:使用预处理器定义,如果在 C 库的 .c 文件中,C 库的头文件将这些函数声明为“内联”,在我的项目中声明这些函数时为“extern inline”。但我不确定他们为什么这样做。 C 库确实有一些使用函数地址作为唯一键值的代码。作为一名 C++ 程序员,我认为这是一种不好的做法,我真的祈祷他们没有那样使用内联函数的地址。我觉得这些 cmets 中有几乎足够的信息可以放入答案,尽管我仍然无法编译程序。
  • 如果它在某处使用函数的地址作为键,并且跨 TU 依赖相同的地址,则不能再使用static inline。不过,即使在 C 模式下也可以使用 -fgnu89-inlineextern inline(请参阅 gcc.gnu.org/onlinedocs/gcc/Inline.html 。如果您在没有 -std=c99 的情况下编译,我认为它甚至可能已经是有效的模式)。
  • 我在这个问题中添加了“C”标签,并且(因为不能有 6 个标签)删除了“multiple-definition-error”,这似乎也被“linker-error”覆盖了。

标签: c++ c linker-errors inline-functions tdm-mingw


【解决方案1】:

首先您必须了解 C99 内联模型 - 可能您的标头有问题。具有外部(非静态)链接的内联函数有两种定义

  • 外部定义
    这个函数的定义在整个程序中只能出现一次,在一个指定的 TU 中。它提供了一个可以从其他 TU 使用的导出函数。

  • 内联定义
    这些出现在每个声明为单独定义的 TU 中。这些定义需要彼此相同或与外部定义相同。如果在库中使用内部,它们可以省略对函数参数的检查,否则这些检查将在外部定义中完成。

函数的每个定义都有自己的局部静态变量,因为它们的局部声明没有链接(它们不像 C++ 中那样共享)。非静态内联函数的定义将是内联定义,如果

  • TU 中的每个函数声明都包含说明符 inline,并且
  • TU 中没有函数声明包含说明符extern

否则,必须出现在该 TU 中的定义(因为内联函数必须在声明的同一 TU 中定义)是外部定义。在调用内联函数时未指定使用外部定义还是内联定义。但是,因为在所有情况下定义的函数仍然是相同的(因为它具有外部链接),所以无论出现多少内联定义,它的地址在所有情况下都比较相等。因此,如果您获取函数的地址,则编译器很可能会解析为外部定义(尤其是在禁用优化的情况下)。

演示inline的错误使用示例,因为它在两个TU中包含两次函数的外部定义,导致多重定义错误

// included into two TUs
void f(void); // no inline specifier
inline void f(void) { }

下面的程序是危险的,因为编译器可以随意使用外部定义,但是程序没有提供一个

// main.c, only TU of the program
inline void g(void) {
  printf("inline definition\n");
}

int main(void) {
  g(); // could use external definition!
}

我使用 GCC 制作了一些测试用例,进一步展示了该机制:

main.c

#include <stdio.h>

inline void f(void);

// inline definition of 'f'
inline void f(void) {
  printf("inline def main.c\n");
}

// defined in TU of second inline definition
void g(void);

// defined in TU of external definition
void h(void);

int main(void) {
  // unspecified whether external definition is used!
  f();
  g();
  h();

  // will probably use external definition. But since we won't compare
  // the address taken, the compiler can still use the inline definition.
  // To prevent it, i tried and succeeded using "volatile". 
  void (*volatile fp)() = &f;
  fp();
  return 0;
}

main1.c

#include <stdio.h>

inline void f(void);

// inline definition of 'f'
inline void f(void) {
  printf("inline def main1.c\n");
}

void g(void) {
  f();
}

main2.c

#include <stdio.h>

// external definition!
extern inline void f(void);

inline void f(void) {
  printf("external def\n");
}


void h(void) {
  f(); // calls external def
}

现在,程序输出了我们预期的结果!

$ gcc -std=c99 -O2 main.c main1.c main2.c
inline def main.c
inline def main1.c
external def
external def

查看符号表,我们会看到内联定义的符号没有导出(来自main1.o),而导出了外部定义(来自main2.o)。


现在,如果您的静态库每个都有其内联函数的外部定义(应该如此),它们自然会相互冲突。解决方案是使内联函数静态或只是重命名它们。这些将始终提供外部定义(因此它们是完整的定义),但它们不会被导出,因为它们具有内部链接,因此不会冲突

static inline void f(void) {
  printf("i'm unique in every TU\n");
}

【讨论】:

  • 这是一个很好的答案。不幸的是,我仍然无法应用这些知识来成功编译我的程序 - 尝试将 extern 内联更改为静态内联使其失败,错误是它混合了同一函数的静态和非静态定义。也许我可以在自己的代码中使用宏来更改包含标题的函数的名称,但是标题中大约有 100 个这样的东西......
  • 有关更多信息,此讨论表明编译器优化级别可能会影响问题:lkml.indiana.edu/hypermail/linux/kernel/0408.0/1787.html
  • 我在使用 MinGW gcc 构建时发现了类似的东西,并发现 MinGW 标头似乎与 -std=gnu99 选项不兼容。当我尝试链接时,这会导致 很多 个多重定义的符号,因为函数的定义被放置在以这种方式构建的每个目标文件中。
  • ...所以我发现使用 -fgnu89-inline 是一种适合我们情况的解决方法,因为我们需要 gnu99 对 for 循环计数器变量的作用域。 (呃)
猜你喜欢
  • 2011-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-12
相关资源
最近更新 更多