我从事一个中型开源项目,该项目需要将原始字节解释为不同类型。这是通过创造性地使用重新解释铸造来实现的。然而,在一个简单的测试用例中,使用 GCC 10 或更高版本进行编译且优化级别高于 O1 时,测试会失败。较低版本的 GCC 和 Clang 不会显示此问题。
我们成功编写了一个可重复的小样本,如下所示。这需要一个 uint64_t 并返回一个指向包含 uint64_t 数据的 uint32_t 数组的指针。预期结果是 bar() 以 uint32_t 形式返回数字 1。
#include <cstddef>
#include <cstdint>
struct RegisterValue {
size_t bytes = 8;
alignas(8) char localValue[16] = {42};
void set(uint64_t value) {
uint64_t* view = reinterpret_cast<uint64_t*>(this->localValue);
view[0] = value;
}
uint32_t* getAsVector() {
if (bytes <= 16) {
return reinterpret_cast<uint32_t*>(this->localValue);
} else {
static uint32_t t = {0xdeadbeef};
return &t;
}
}
};
extern "C" uint32_t bar() {
RegisterValue v;
v.set(0x0000000200000001);
return v.getAsVector()[0];
}
您可以在此处使用编译器资源管理器中的代码:https://godbolt.org/z/1eYs4orKc
本质上,所有的reinterpret_casting都被GCC积极优化,导致汇编只返回一个立即数。这是预料之中的。然而,在较低的优化级别 (O1) 下,这会返回预期结果 (1),但在较高的优化级别 (O2、O3) 下,这会错误地返回 42。 使这更加令人困惑的是几乎对源的任何更改(例如删除 if/否则将第一个返回保留在 getAsVector 中)会导致预期的行为。
这听起来像 UB,但我们已经阅读了 https://en.cppreference.com/w/cpp/language/reinterpret_cast 并且不能自信地说这是否是 UB?如果是,那么我们需要更新我们的实现,如果不是,这将表明存在编译器错误。
任何见解都会有帮助。
是的,您所做的是未定义行为,类型别名冲突,因为您通过
localValue
访问您的 uint64_t*
。根据 reinterpret_cast,uint64_t*
可以通过 char*
进行类型访问,但反之则不然。第一部分可以用 std::memcpy
: 修复
void set(uint64_t value) {
std::memcpy(localValue, &value, sizeof(value));
}
但是如果不引入额外的类字段来保存结果,我对
uint32_t* getAsVector()
没有任何直接的想法。您可以返回 const char*
并让用户处理 uint32
转换,或者返回 std::vector<std::uint32_t>
。