代码之家  ›  专栏  ›  技术社区  ›  Dennis

链接静态libs时内联函数的多个定义

  •  11
  • Dennis  · 技术社区  · 15 年前

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

    在我看来,C库的一部分功能是在内联函数中实现的。当你使用C库的API时,你不能避免包含内联函数,但是当我尝试把它链接在一起时,我得到链接错误,说所有内联函数都有多个定义——我在C++包装库中调用的两个函数,而我没有的,基本上定义在头上的任何东西都得到了。十在C库和C++库中为它创建的函数。

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

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

    (仅供参考,第三方库在其所有头文件中都包含了ifdef uucplusplus和extern“c”保护;无论如何,如果这是问题,它不会导致符号的多重定义,它会导致相反的问题,因为符号是未定义的或至少是不同的。)

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

    以前也有人问过类似的问题,但是我没有找到任何一个问题的答案:

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

    这是一个MSVC程序,但是我在使用MIWW;而且,这个问题中的海报的问题是在一个标题中定义了类类构造函数的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

    1 回复  |  直到 13 年前
        1
  •  14
  •   Johannes Schaub - litb    15 年前

    首先,您必须了解C99内联模型——也许您的头有问题。对于具有外部(非静态)链接的内联函数,有两种定义

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

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

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

    • tu中的每个函数声明都包含说明符 inline
    • tu中没有包含说明符的函数声明 extern .

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

    演示错误使用 内联的 ,因为它在两个tus中包含两次函数的外部定义,从而导致多个定义错误

    // 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做了一些测试案例,进一步演示了这种机制:

    主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;
    }
    

    MIN 1.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();
    }
    

    MIN 2.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");
    }
    
    推荐文章