C 和 C++ 中的类型双关 (Type Punning)
Type Punning in C and C++

原始链接: https://blog.pwkf.org/2026/09/21/correct-type-punning-in-c.html

类型双关(Type punning)——即将同一内存区域解读为不同类型——对于底层编程至关重要,但由于严格别名规则,它常导致难以察觉的隐蔽错误。虽然指针转换(如 `(int*)&float_var`)看似有效,但这属于未定义行为。在高优化等级(-O2 及以上)下,编译器会假定不相关的类型不会发生别名,因此可能会基于“对一个指针的写入不会影响另一个指针的值”这一错误假设,进而优化掉部分代码。 在 C++ 中,这个问题尤为严重,因为编译器在利用别名假设以提升性能方面更加激进。因此,在调试构建中运行正常的代码,在生产环境中可能会失效。 为了确保行为明确并避免与优化相关的错误,开发者应避免使用指针转换来进行类型双关。取而代之的是,应使用以下安全替代方案: * **联合体(Unions):** C 语言明确允许通过不同的成员访问同一内存。 * **`memcpy`:** 类型双关的黄金标准。现代编译器能识别这种模式,并将其优化为单条寄存器移动指令,在确保安全性和高性能的同时,不会引入未定义行为的风险。

Hacker News 最新 | 往期 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 C 和 C++ 中的类型双关 (Type Punning) ( pwkf.org ) 11 点 由 ingve 30 分钟前 | 隐藏 | 往期 | 收藏 | 1 条评论 帮助 K0IN 3 分钟前 [–] C++ 中的 reinterpret_cast 对此有效吗? 回复 社区准则 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系我们 搜索:
相关文章

原文

I had a bug that took me a while to track down. The problem was type punning. A pointer cast worked fine at -O0 and silently broke at -O2. The C vs C++ distinction here is genuinely treacherous, and most blog posts on the topic get it wrong.

Type punning is interpreting memory as different types between reads and writes. It’s essential for serialisation, network protocols, and low-level hardware access.

The problem is that “works in practice” and “has defined behaviour” are different things.

The Spectrum from Safe to UB

In C, the safe ways to type pun are union and memcpy. Pointer casts are technically undefined behavior under strict aliasing rules, even though they work on every compiler you’ll encounter.

Unions

A union lets you write as one type and read as another. This is defined behavior in C:

union {
    float f;
    uint32_t bits;
} pun;

pun.f = 3.14f;
uint32_t exp = (pun.bits >> 23) & 0xff; // extract IEEE-754 exponent

This also works beautifully for pulling apart structs:

union {
    struct color { float r, g, b, a; } c;
    float as_array[4];
} u;

u.c = (struct color){ .r = 1, .a = 1 };
float a = u.as_array[3];

memcpy

If you don’t want a union, memcpy is safe and the compiler will optimise it to a register move:

float f = 3.14f;
int i;
memcpy(&i, &f, sizeof(f)); // defined behavior, compiles to a single instruction

Pointer Casts — Convenient but UB

This compiles, runs, and gives you the “right” answer on every platform:

float f = 3.14f;
int *p = (int *)&f;
int i = *p;

It’s also undefined behavior. The strict aliasing rule says an object shall only be accessed through an lvalue of its effective type, a qualified version of it, or a character type. A pointer cast to an unrelated type violates this.

Why C and C++ Differ

In C, types are a way to interpret memory. In C++, types are first-class citizens — the compiler is allowed to assume that different types never alias each other.

This has concrete consequences. Consider:

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;
    }

    return c->b;
}

int main() {
    struct c c = { 2, 3 };
    return bar((uint64_t *) &c, &c);
}

With GCC or Clang at -O2, this returns 2. At -O1 or below, it returns 0. The compiler sees that u64 is uint64_t* and c is struct c* — different types — so it assumes they don’t alias. The second c->a == 2 check gets optimised away based on the assumption that writing *u64 = 4 can’t change c->a. This is technically correct under the standard, even though the types do overlap in memory.

The deeper explanation is in Taking a Byte Out of C++ - Avoiding Punning by Starting Lifetimes, which covers why C++ went this direction.

The Practical Rule

If you’re writing C and need to type pun, use a union or memcpy.

Pointer casts “work” until they don’t. And “don’t” means the compiler silently optimises away the code you thought was executing. If you’re writing C++, the same applies, plus the compiler has more latitude to break things under the as-if rule.

The bug I started with? A pointer cast from float* to uint32_t* in a hot loop. At -O2, the loop was optimised under strict aliasing assumptions, and the values I was writing never appeared where I expected them. A union fixed it in ten minutes.

联系我们 contact @ memedata.com