首页 时政热点 科技头条 智能AI 安全攻防 数码硬件 开发者生态 汽车 游戏 社会热点 开源推荐 医疗健康 归档 标签 关于

C 和 C++ 中的类型双关语

2026-09-22 1 阅读 约5分钟阅读 ingve
分享:
字号:
我遇到了一个错误,花了我一段时间才找到。问题在于类型双关。指针转换在 -O0 处工作正常,但在 -O2 处默默地崩溃。这里 C 与 C++ 的区别确实很危险,大多数关于该主题的博客文章都弄错了。类型双关是将内存解释为读取和写入之间的不同类型。它对于序列化、网络协议和低级硬件访问至关重要。问题在于“在实践中有效”和“已定义行为”是不同的事情。从安全到 UB 的范围 在 C 语言中,输入双关语的安全方法是 union 和 memcpy 。在严格的别名规则下,指针强制转换在技术上是未定义的行为,尽管它们适用于您将遇到的每个编译器。联合 联合允许您以一种类型编写并以另一种类型读取。这是 C 中定义的行为: union { float f ; uint32_t 位;双关语;双关语。 f = 3 。 14楼; uint32_t exp = ( 双关语 . 位 >> 23 ) & 0xff ; // 提取 IEEE-754 指数 这对于分离结构也非常有效: union { struct color { float r , g , b , a ; } c;浮动as_array [4]; } 你;你。 c = ( 结构颜色 ){ . r = 1 , . a = 1 };浮动 a = u 。 as_array[3]; memcpy 如果您不需要联合,memcpy 是安全的,编译器会将其优化为寄存器移动: float f = 3 。 14楼;整数我; memcpy ( & i , & f , sizeof ( f )); // 定义的行为,编译为单个指令 指针转换 — 方便但 UB 这会编译、运行并在每个平台上为您提供“正确”答案: float f = 3 。 14楼; int * p = ( int * ) & f ; int i = * p ;这也是未定义的行为。严格的别名规则规定,对象只能通过其有效类型的左值、其限定版本或字符类型来访问。将指针强制转换为不相关的类型违反了这一点。为什么 C 和 C++ 不同 在 C 中,类型是解释内存的一种方式。在 C++ 中,类型是一等公民——编译器可以假设不同的类型永远不会互相别名。这会产生具体的后果。考虑: struct c { uint32_t a ; uint32_t b ; }; uint32_t bar ( uint64_t * u64 , struct c * c ) { if ( c -> a == 2 ) { * u64 = 4 ; } if ( c -> a == 2 ) { return c -> a ;返回 c -> b ; } int main () { struct c c = { 2 , 3 };返回栏 (( uint64_t * ) & c , & c );对于 GCC 或 Clang 的 -O2 ,这将返回 2 。在 -O1 或以下,它返回 0 。编译器认为 u64 是 uint64_t* 而 c 是 struct c* — 不同的类型 — 因此它假设它们没有别名。第二个 c->a == 2 检查基于编写 *u64 = 4 不能更改 c->a 的假设而被优化。在标准下,这在技术上是正确的,即使类型在内存中确实重叠。更深入的解释在 Take a Byte Out of C++ - Savings Punning by Start Lifetimes 中,其中涵盖了为什么 C++ 会朝这个方向发展。实用规则如果您正在编写 C 语言并且需要输入双关语,请使用 union 或 memcpy 。指针会强制执行“工作”,直到不再执行为止。 “不”意味着编译器默默地优化掉你认为正在执行的代码。如果您正在编写 C++,同样适用,而且编译器有更大的自由度来破坏 as-if 规则下的事物。我开始的错误?在热循环中从 float* 转换为 uint32_t* 的指针。在 -O2 处,循环在严格的别名假设下进行了优化,并且我正在编写的值从未出现在我预期的位置。工会在十分钟内就把它修好了。
这篇文章对您有帮助吗?

订阅66必读

每日精选科技资讯,直达你的邮箱