关于reinterpret_cast相关未定义行为的澄清问询
核心结论
你的理解大部分准确,下面针对你的问题逐一拆解验证:
1. 通过unsigned char*访问/修改结构体字节的合法性
C++标准明确允许:任何对象的存储都可以通过unsigned char*(或char*、signed char*)进行检查和修改,这是定义行为,不受严格别名规则限制。只要确保操作的字节在对象sizeof范围内,就不会触发UB。
你的这段代码完全合法:
struct __attribute__((packed)) SimpleStruct { uint32_t a = 0; uint8_t b = 1; int16_t c = 2; uint8_t d[5] = {0, 1, 2, 3, 4}; }; SimpleStruct simple_struct; unsigned char *bytes_of_simple_struct = reinterpret_cast<unsigned char *>(&simple_struct); static_assert(sizeof(simple_struct) == 12); bytes_of_simple_struct[0] = 0x1U;
修改后访问simple_struct.a的行为是定义的——前提是写入的字节组合是uint32_t的有效表示。
2. 基础数值类型的有效表示
- 无符号整数类型(
uint8_t、uint16_t等):标准规定所有可能的字节组合都是有效表示,对应的值按“纯二进制”编码规则解析(现代平台基本都是二进制补码,无符号的补码和原码一致)。 - 有符号整数类型(
int16_t等):如果系统使用二进制补码(几乎所有现代系统都是),所有字节组合都是有效表示;标准允许其他编码(原码、反码),这种情况下部分字节组合可能无效,但实际开发中无需考虑特殊平台的情况。
简言之:基础数值类型(尤其是无符号)的任意字节组合,在现代平台上都是有效表示,你的理解正确。
3. bool类型的无效表示问题
C++标准规定bool对象只能持有true或false,但具体的底层存储值是实现定义的(通常false对应0字节,true对应非0字节,但编译器可以选择任意非0值)。关键是:如果通过字节修改让bool对象的存储值不属于实现定义的有效表示集合,后续访问该bool对象的行为就是未定义的。
比如你的这段代码:
struct __attribute__((packed)) SimpleStruct { bool a_bool = false; uint8_t b = 1; int16_t c = 2; uint8_t d[5] = {0, 1, 2, 3, 4}; }; SimpleStruct simple_struct; unsigned char *bytes_of_simple_struct = reinterpret_cast<unsigned char *>(&simple_struct); bytes_of_simple_struct[0] = 0xFFU; assert(simple_struct.a_bool == false);
修改a_bool的字节为0xFF后,访问simple_struct.a_bool的行为是UB——因为0xFF可能被编译器解析为true,此时断言失败;更关键的是,若编译器不认可0xFF为bool的有效表示,直接访问就会触发UB。
额外注意:packed属性的影响
__attribute__((packed))是编译器扩展(非标准),它移除结构体填充字节的同时,可能导致成员对齐不符合标准要求。通过unsigned char*访问字节的行为依然合法,但直接访问未对齐的成员(比如int16_t c在packed结构体中可能不对齐),部分平台会触发硬件异常——这是实现定义行为,而非标准UB,但仍需谨慎。
内容的提问来源于stack exchange,提问作者haydenridd

