header only libraries 不同版本的使用是否导致UB

Does the usage of header only libraries with different versions result in UB

假设我有一个库 somelib.a,它由包管理器以二进制形式分发。这个库使用了 header 唯一的库 anotherlib.hpp.

如果我现在 link 我的程序反对 somelib.a,并且还使用 anotherlib.hpp 但版本不同,那么这可能会导致 UB,如果 somelib.a 使用include headers.

anotherlib.hpp 的部分内容

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

版本

我问的原因是如果我 link 我的程序的各个编译单元到最终程序,然后 linker 删除重复的符号(取决于它是否是内部的 link年龄与否)。因此,只有 header 的库通常以可以删除重复符号的方式编写。

一个最小的例子

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 不兼容,然后应用程序是 link 针对构建的 somelib.a在版本为 3.2

的系统上

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++标准规定,如果在一个程序中有一个内联函数(或class模板,或变量等)的多个定义,并且所有定义不一样,那你就有UB了。

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

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

6.2 One-definition rule

[...]

There can be more than one definition of a class type (Clause 12), enumeration type (10.2), inline function with external linkage (10.1.6), inline variable with external linkage (10.1.6), class template (Clause 17), non-static function template (17.5.6), static data member of a class template (17.5.1.3), member function of a class template (17.5.1.1), or template specialization for which some template parameters are not specified in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements.

Given such an entity named D defined in more than one translation unit, then

  • each definition of D shall consist of the same sequence of tokens; and

  • in each definition of D, corresponding names, looked up according to 6.4, shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (16.3) and after matching of partial template specialization (17.8.3), except that a name can refer to (6.2.1)

    • a non-volatile const object with internal or no linkage if the object

      • has the same literal type in all definitions of D, (6.2.1.2)

      • is initialized with a constant expression (8.20),

      • is not odr-used in any definition of D, and

      • has the same value in all definitions of D,

    or

    • a reference with internal or no linkage initialized with a constant expression such that the reference refers to the same entity in all definitions of D; and (6.3)
  • in each definition of D, corresponding entities shall have the same language linkage; and

  • in each definition of D, the overloaded operators referred to, the implicit calls to conversion functions, constructors, operator new functions and operator delete functions, shall refer to the same function, or to a function defined within the definition of D; and

  • in each definition of D, a default argument used by an (implicit or explicit) function call is treated as if its token sequence were present in the definition of D; that is, the default argument is subject to the requirements described in this paragraph (and, if the default argument has subexpressions with default arguments, this requirement applies recursively).28

  • if D is a class with an implicitly-declared constructor (15.1), it is as if the constructor was implicitly defined in every translation unit where it is odr-used, and the implicit definition in every translation unit shall call the same constructor for a subobject of D.

If D is a template and is defined in more than one translation unit, then the preceding requirements shall apply both to names from the template’s enclosing scope used in the template definition (17.6.3), and also to dependent names at the point of instantiation (17.6.2). If the definitions of D satisfy all these requirements, then the behavior is as if there were a single definition of D. If the definitions of D do not satisfy these requirements, then the behavior is undefined.