重新解释的值因编译器而异

Reinterpret casted value varies by compiler

本文关键字:编译器 新解释 解释      更新时间:2023-10-16

对于同一程序:

const char* s = "abcd";
auto x1 = reinterpret_cast<const int64_t*>(s);
auto x2 = reinterpret_cast<const char*>(x1);
std::cout << *x1 << std::endl;
std::cout << x2 << std::endl; // Always "abcd"

在gcc5(链接):139639660962401
在gcc8(链接)中:1684234849

  1. 为什么值会因编译器版本的不同而不同
  2. 那么,什么是编译器安全的方式来从const char*移动到int64_t并向后移动(就像在这个问题中一样——不是针对实际的整数字符串,而是包含其他字符的字符串)
  1. 为什么值会因编译器版本的不同而不同

行为未定义。

  1. 从const char*向后移动到int64_t的编译器安全方法是什么

"从constchar*移动到int64_t"的意思有些不清楚。基于这个例子,我假设你的意思是创建一个从字符序列(长度不大于fit)到64位整数的映射,这种映射可以使用另一个进程转换回来——可能由另一个(版本)编译器编译。

首先,创建一个int64_t对象,初始化为零:

int64_t i = 0;

获取字符串的长度

auto len = strlen(s);

检查是否符合

assert(len < sizeof i);

将字符序列的字节复制到整数上

memcpy(&i, s, len);

(只要整数类型没有陷阱表示形式)行为定义良好,只要CPU端序(和负数表示形式)保持不变,生成的整数在编译器版本中就会相同。

读回字符串不需要复制,因为char在特殊情况下被允许对所有其他类型进行别名:

auto back = reinterpret_cast<char*>(&i);

注意最后一节中的资格。如果整数(例如通过网络)传递给在另一个CPU上运行的进程,则此方法不起作用。这也可以通过比特移位和屏蔽来实现,这样你就可以使用比特移位和掩蔽将八位字节复制到特定的重要位置。

当您取消引用int64_t指针时,它正在读取为您从中投射的字符串分配的内存末尾。如果将字符串的长度更改为至少8个字节,则整数值将变得稳定。

const char* s = "abcdefg"; // plus null terminator
auto x1 = reinterpret_cast<const int64_t*>(s);
auto x2 = reinterpret_cast<const char*>(x1);
std::cout << *x1 << std::endl;
std::cout << x2 << std::endl; // Always "abcd"

如果您想将指针存储在整数中,则应该使用intptr_t并省略*,如:

const char* s = "abcd";
auto x1 = reinterpret_cast<intptr_t>(s);
auto x2 = reinterpret_cast<const char*>(x1);
std::cout << x1 << std::endl;
std::cout << x2 << std::endl; // Always "abcd"

根据RemyLebeau在你的帖子评论中指出的,

unsigned 5_byte_mask = 0xFFFFFFFFFF; std::cout << *x1 & 5_byte_mask << std::endl;

无论使用什么编译器,都应该是在小端序机器上获得相同值的合理方法。根据一个或另一个规范,它可能是UB,但从编译器的角度来看,您正在取消对已初始化五个字节的有效地址处的八个字节的引用,并屏蔽未初始化/垃圾数据的剩余字节。