使用具有不同版本的标头库是否会导致UB

问题描述 投票:3回答:1

让我们假设我有一个库somelib.a,它由包管理器以二进制形式分发。此库使用标题库anotherlib.hpp

如果我现在将我的程序与somelib.a链接,并且还使用anotherlib.hpp但使用不同的版本,那么这可能导致UB,如果somelib.a在其anotherlib.hpp标题中使用include的部分。

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

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

一个最小的例子

somelib.a建立在nlohmann / json.hpp版本3.2的系统上

somelib / somelib.h

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

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

somelib.cpp

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

应用程序构建在另一个系统上,使用nlohmann / json.hpp版本3.5和3.2和3.5不兼容,然后将应用程序与版本为3.2的系统上构建的somelib.a链接

application.cpp

#include <somelib/somelib.h>
#include <nlohmann/json.hpp>
#include <ifstream>

int main() {
   auto c = somelib::read_configuration("config.json");

   nlohmann::json j;
   std::ifstream i("another.json");

   i >> j;

   return 0;
}
c++ static-libraries static-linking
1个回答
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),就好像构造函数是在每个使用odr的翻译单元中隐式定义的,并且每个翻译单元中的隐式定义应该调用相同的构造函数D.的子对象

如果D是模板并且在多个转换单元中定义,那么前面的要求既适用于模板定义中使用的模板封闭范围中的名称(17.6.3),也适用于实例化时的相关名称。 (17.6.2)。如果D的定义满足所有这些要求,则行为就好像存在D的单个定义。如果D的定义不满足这些要求,那么行为是不确定的。

© www.soinside.com 2019 - 2024. All rights reserved.