链接静态库时内联函数的多重定义

发布于 2024-08-20 22:50:09 字数 1571 浏览 8 评论 0原文

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

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

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

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

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

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

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

这个问题的答案问题是海报多重定义变量,我的问题是内联函数的多重定义:在多个 cpps 中包含相同标头导致重复的多个定义错误

这是一个 MSVC 程序,但我正在使用 mingw;另外,发帖者在这个问题中的问题是标头中类主体之外的 C++ 类构造函数的定义,而我的问题是内联的 C 函数: 静态库多重定义问题

这个傻瓜将他所有的 C 代码重命名为 C++ 文件,并且他的 C 代码不是 C++ 安全的:链接时大量 std:: 函数的多重定义

这只是想要要知道为什么违反单一定义规则不是错误:不可预测的行为具有不同定义的内联函数

I have a C++ program that I compile with mingw (gcc for Windows). Using the TDM release of mingw which includes gcc 4.4.1. The executable links to two static library (.a) files: On of them is a third-party library written in C; the other is a C++ library, written by me, that uses the C library provides my own C++ API on top.

An (in my view, excessive) portion of the C library's functionality is implemented in inline functions. You can't avoid including the inline functions when you use the C library's API, but when I try to link it all together, I'm getting link errors saying there is a multiple definition of all of the inline functions - both ones I have called in my C++ wrapper library and ones which I have not, basically anything defined inline in the headers has gotten a function created for it in both the C library and the C++ library.

It doesn't cause multiple definition errors when the include files are used multiple times in different .c or .cpp files in the same project; the problem is just that it generates one definition per library.

How/why is the compiler generating functions and symbols for these inline functions in both libraries? How can I force it to stop generating them in my code? Is there a tool I can run to strip the duplicate functions from the .a file, or a way to make the linker ignore multiple definitions?

(FYI, the third party library does include #ifdef __cplusplus and extern "C" guards in all its headers; anyway if that were the problem, it would not cause a multiple definition of symbol, it would cause the opposite problem because the symbol would be undefined or at least different.)

Notably, the link errors do NOT occur if I link to the third party C library's DLL; however then I get strange runtime failures that seem to have to do with my code having its own version of functions it should be calling from the DLL. (As if the compiler is creating local versions of functions I didn't ask for.)

Similar versions of this question have been asked before, however, I didn't find the answer to my situation in any of these:

The answer to this question was that the poster was multiply defining variables, my problem is multiple definition of inline functions: Repeated Multiple Definition Errors from including same header in multiple cpps

This was an MSVC program, but I'm using mingw; also, the poster's problem in this question was the definition of a C++ class constructor outside of the class body in a header, while my problem is with C functions that are inline: Static Lib Multiple Definition Problem

This fool renamed all his C code as C++ files and his C code wasn't C++-safe: Multiple definition of lots of std:: functions when linking

This one was just wanting to know why violating the one definition rule was not an error: unpredictable behavior of Inline functions with different definitions

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

童话 2024-08-27 22:50:10

首先,您必须了解 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");
}

First you have to understand the C99 inline model - perhaps there is something wrong with your headers. There are two kind of definitions for inline functions with external (non-static) linkage

  • External definition
    This definition of a function can only appear once in the whole program, in a designated TU. It provides an exported function that can be used from other TUs.

  • Inline definition
    These appear in every TU where declared as a separate definition. The definitions do not need to be identical to one another or to the external definition. If used internal in a library, they can do omit checking on function arguments that would otherwise be done in the external definition.

Each definition of the function has its own local static variables, because their local declarations have no linkage (they are not shared like in C++). A definition of a non-static inline function will be an inline definition if

  • Every function declaration in a TU includes the specifier inline, and
  • No function declaration in a TU includes the specifier extern.

Otherwise, the definition that must appear in that TU (because inline functions must be defined in the same TU where declared) is an external definition. In a call to an inline function it's unspecified whether the external or the inline definition is used. However, because the function defined in all cases is still the same (because it has external linkage), the address of it compares equal in all cases, no matter how many inline definitions appear. So if you take the function's address, it's likely the compiler resolves to the external definition (especially if optimizations are disabled).

An example that demonstrates a wrong use of inline, because it includes an external definition of a function twice in two TUs, causing a multiple definition error

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

The following program is dangerous, because the compiler is free to use the external definition, but the program does not provide one

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

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

I have made some test cases using GCC that demonstrate the mechanism further:

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
}

Now, the program outputs what we expected!

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

Looking at the symbol table, we will see that the symbol of an inline definition is not exported (from main1.o), while an external definition is exported (from main2.o).


Now, if your static libraries each have an external definition of their inline functions (as they should), they will naturally conflict with each other. The solution is to make the inline functions static or just to rename them. These will always provide external definitions (so they are full fledged definitions), but they are not exported because they have internal linkage, thus not conflicting

static inline void f(void) {
  printf("i'm unique in every TU\n");
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文