使用具有不同版本的仅标头库是否会导致 UB
Does the usage of header only libraries with different versions result in UB
假设我有一个库somelib.a
,它由包管理器作为二进制分发。并且该库仅使用标头库anotherlib.hpp
。
如果我现在将我的程序链接到somelib.a
,并且还使用anotherlib.hpp
但具有不同的版本,那么如果somelib.a
在其include
标头中使用部分anotherlib.hpp
,则可能会导致 UB。
但是,如果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
应用.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++标准指出,如果程序中有多个内联函数(或类模板或变量等)的定义,并且所有定义都不相同,那么您就有了 UB。
实际上,这意味着除非标头库的 2 个版本之间的更改非常有限,否则您将拥有 UB。 例如,如果唯一的更改是空格更改、注释或添加新符号,则不会有未定义的行为。但是,如果更改了现有函数中的一行代码,则它是 UB。
来自 C++17 最终工作草案 (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 的任何定义中都没有使用 ODR,并且
在 D 的所有定义中具有相同的值,
或
- 具有内部链接或无链接的引用,使用常量表达式初始化 使得引用在所有定义中引用同一实体 的 D;和 (6.3)
在 D 的每个定义中,相应的实体 应具有相同的语言联系;和
在每个定义中 的 D,引用的重载运算符,隐式调用 转换函数、构造函数、运算符新函数和 运算符删除函数,应指同一函数,或 在 D 定义中定义的函数;和
在每个定义中 D,(隐式或显式)函数调用使用的默认参数 被视为其令牌序列存在于 D;也就是说,默认参数受制于要求 本段中描述(并且,如果默认参数具有 带有默认参数的子表达式,此要求适用 递归).28
如果 D 是具有隐式声明的类 构造函数 (15.1),就好像构造函数是隐式定义的 在每个使用 ODR 的翻译单元中,以及隐式 每个翻译单元中的定义应调用相同的构造函数 对于 D 的子对象。
如果 D 是模板,并且在多个翻译单元中定义, 则上述要求应同时适用于 模板定义中使用的模板封闭范围 (17.6.3), 以及在实例化点 (17.6.2) 的依赖名称。如果 D的定义满足所有这些要求,然后行为 就好像D有一个单一的定义。 不满足这些要求,则行为是未定义的。
- 在提升multi_index容器中,是否定义了"default index"?
- 在C++STL中是否有Polyval(Matlab函数)等价物?
- 检查输入是否不是整数或数字
- 是否可以初始化不可复制类型的成员变量(或基类)
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 是否可以通过C++扩展强制多个python进程共享同一内存
- 此代码是否违反一个定义规则
- 是否需要删除包含对象的"pair"?
- 使用 CTRP 时,是否访问访问父构造函数 UB 中的子属性?
- 传递给放置 new 的指针是否是指向其对象表示形式的非 UB 指针?
- 是否通过单元化指针UB访问静态类成员
- 是否可以默认使用UB
- 是否有任何编译器标志可以在下面的代码中用于报告有关 UB 的警告?
- 使用具有不同版本的仅标头库是否会导致 UB
- 移动指针是否经过结构成员 UB?并访问它?
- 是否有任何关于带有UB的代码应该是可访问的保证
- 由于在序列点之间修改变量两次而导致的 UB 是否会转移到"inner"范围?
- 在c++ 11或更高版本中,是否有一种方法可以让constexpr在没有UB的情况下确定endian ?
- 此代码是否导致UB
- 是否将一个无符号整数右移其总位数UB