C++中通过指针强转重新解释内存数据是否为未定义行为?
C++类型双关相关未定义行为问题解答
问题1:uint8_t转int8_t的未定义行为原因
你看到的“取值超出范围”的解释,本质上指向的是C++标准对有符号整数表示的规定,以及陷阱表示的风险:
- 在C++20之前,标准没有强制要求有符号整数必须使用补码实现,允许原码、反码、补码三种实现方案:
- 补码实现下
0b10000000作为int8_t的值是-128 - 原码实现下该值为
-0,部分实现不支持负0,该值会被判定为陷阱表示 - 反码实现下该值为
-127
只要访问陷阱表示就会触发未定义行为,标准不对后续行为做任何保证。
- 补码实现下
- 还要注意严格别名规则的约束:如果你的实现中
int8_t是signed char的别名、uint8_t是unsigned char的别名,那字符类型互访是符合严格别名规则的;但如果某些特殊实现中int8_t不属于字符类型范畴,那该写法本身也违反严格别名规则,直接属于未定义行为。
C++20之后标准强制有符号整数使用补码实现,这种情况下该写法就有明确定义,b的值保证为-128。
问题2:快速逆平方根中float转int32_t写法的未定义行为原因
这种写法的核心问题是违反C++严格别名规则:
C++标准规定,一个对象的存储只能通过以下几种类型的左值访问:
- 对象的实际类型
- 对象实际类型的const/volatile限定版本
- 与对象实际类型对应的有符号/无符号版本
- 字符类型(
char、signed char、unsigned char) - 包含上述类型的聚合/联合体类型
显然int32_t和float不属于上面的任意一种兼容情况,所以不管内存是否对齐、存储内容是否合法,这种直接通过不同类型指针解引用 reinterpret 内存的写法都是未定义行为。
编译器开启优化时很容易出现不符合预期的结果:比如编译器认为float类型的对象不会被int32_t类型的指针修改,就会把y的值缓存在寄存器中不回写到内存,最后读取i的时候拿到的就是内存里的脏数据。
正确的类型双关实现方式
如果你需要在不同类型之间重新解释内存内容,标准允许的写法有两种:
- C++11及以上可以用
memcpy:
float y = 1.5f; int32_t i; memcpy(&i, &y, sizeof(i)); // 完全合法,无未定义行为
- C++20及以上可以用标准库提供的
std::bit_cast,写法更简洁:
float y = 1.5f; int32_t i = std::bit_cast<int32_t>(y);
常见误区说明
很多开发者会觉得“我这么写在x86上跑起来没问题,怎么会是UB”,这是混淆了平台实现行为和C++标准定义行为的区别:未定义行为不代表程序一定会崩溃或者出错,只是标准不对它的行为做任何保证,换编译器、换编译选项、换平台都有可能出现完全不符合预期的结果。
内容的提问来源于stack exchange,提问作者Lala5th
相关产品推荐
相关产品推荐

