是否仅使用标头库使用不同版本会导致UB

时间:2019-03-13 09:13:24

标签: c++ static-libraries static-linking

让我们假设我有一个库somelib.a,它由程序包管理器以二进制形式分发。而且该库仅使用标头库anotherlib.hpp

如果我现在将程序链接到somelib.a,并且还使用anotherlib.hpp但版本不同,则如果somelib.a使用{{1 }}在其anotherlib.hpp标头中。

但是,如果include仅在其cpp文件中引用/使用somelib.a(会导致我不知道它使用它们),会发生什么?我的应用程序和anotherlib.hpp之间的链接步骤是否可以确保somelib.a和我的应用程序都使用自己的somelib.a版本。

我问的原因是,如果我将程序的各个编译单元链接到最终程序,则链接器将删除重复的符号(取决于它是否是内部链接)。因此,通常只写头文件库的方式就可以删除重复的符号。

一个简单的示例

anotherlib.hpp构建在具有nlohmann / json.hpp 3.2版的系统上

somelib / somelib.h

somelib.a

somelib.cpp

namespace somelib {
  struct config {
    // some members
  };

  config read_configuration(const std::string &path);
}

应用程序是在另一个具有nlohmann / json.hpp版本3.5和3.2和3.5的系统上构建的,然后将应用程序与在版本3.2的系统上构建的#include <nlohmann/json.hpp> namespace somelib { config read_configuration(const std::string &path) { nlohmann::json j; std::ifstream i(path); i >> j; config c; // populate c based on j return c; } } 进行链接

application.cpp

somelib.a

1 个答案:

答案 0 :(得分:4)

使用静态库几乎没有什么不同。

C ++标准规定,如果在程序中,内联函数(或类模板或变量等)有多个定义,并且所有定义都不相同,则您拥有UB。

实际上,这意味着除非头文件库的两个版本之间的更改非常有限,否则您将拥有UB。 例如,如果唯一的更改是空格更改,注释或添加新符号,那么您将不会有未定义的行为。但是,如果更改了现有函数中的单行代码,则为UB。

来自C++17 final working draft (n4659.pdf)

  

6.2一定义规则

     

[...]

     

一个类类型可以有多个定义(第12条),   枚举类型(10.2),具有外部链接的内联函数   (10.1.6),带有外部链接的内联变量(10.1.6),类   模板(条款17),非静态功能模板(17.5.6),静态   类模板的数据成员(17.5.1.3),类的成员函数   模板(17.5.1.1),或某些模板专门化   如果每个定义出现在不同的翻译单元中,并且前提条件满足以下定义,则在程序中未指定模板参数:   以下要求。

     

给出这样一个名为D的实体,该实体在多个翻译中定义   单位,然后

     
      
  • D的每个定义均应包含相同的内容   令牌顺序;和

  •   D的每个定义中的
  •   根据6.4查找的名称,应指定义的实体   在D的定义之内,或者在指代同一实体之后   重载分辨率(16.3)和部分模板匹配后   专业化(17.8.3),只是名称可以引用(6.2.1)

         
        
    • 具有内部链接或不具有链接的非易失性const对象,如果该对象

           
          在D的所有定义中,
      • 具有相同的文字类型,   (6.2.1.2)

      •   
      • 用常量表达式(8.20)初始化

      •   
      • 在D的任何定义中均未使用,并且

      •   
      • 在D的所有定义中具有相同的值,

      •   
    •   
         

         
        
    • 内部链接或无链接的引用都用常量表达式初始化   这样,引用在所有定义中都指代同一实体   的D;和(6.3)
    •   
  •   D的每个定义中的
  • ,对应的实体   应当具有相同的语言联系;和

  •   每个定义中的
  •   D,所指的重载运算符,对的隐式调用   转换函数,构造函数,运算符新函数和   操作员删除功能,应指相同的功能,或指   在D的定义内定义的功能;和

  •   
  • 在每个定义中   D,(隐式或显式)函数调用使用的默认参数   视为其令牌序列出现在的定义中   D;也就是说,默认参数取决于要求   在本段中进行了说明(如果默认参数包含   具有默认参数的子表达式,此要求适用   递归).28

  •   
  • 如果D是具有隐式声明的类   构造函数(15.1),就好像该构造函数是隐式定义的   在奇数个使用的翻译单元中,以及隐式   每个翻译单元中的定义应调用相同的构造函数   D的子对象。

  •   
     

如果D是模板并且在多个翻译单元中定义,   那么前述要求应同时适用于来自   模板定义(17.6.3)中使用的模板的封闭范围,   以及实例化时的从属名称(17.6.2)。如果   D的定义满足所有这些要求,那么行为   好像有一个D的定义。如果D的定义   不满足这些要求,则行为是不确定的。