我对 C++ 严格别名规则及其可能的影响感到困惑。考虑以下代码:
int main() {
int32_t a = 5;
float* f = (float*)(&a);
*f = 1.0f;
int32_t b = a; // Probably not well-defined?
float g = *f; // What about this?
}
查看 C++ 规范,第 3.10.10 节,从技术上讲,给定的代码似乎都没有违反那里给出的“别名规则”:
如果程序试图通过以下类型之一以外的左值访问对象的存储值,则行为未定义:
... 合格访问器类型列表 ...
*f = 1.0f;
不会违反规则,因为无法访问存储的值,即我只是通过指针写入内存。我不是从记忆中读取或试图在这里解释一个值。- 该行
int32_t b = a;
不违反规则,因为我是通过其原始类型访问的。 float g = *f;
出于同样的原因,这条线并没有违反规则。
在另一个线程中,成员 CortAmmon 实际上在响应中提出了相同的观点,并补充说,通过写入活动对象产生的任何可能的未定义行为,如 中 *f = 1.0f;
,将由标准的“对象生命周期”定义(似乎是对于 POD 类型来说微不足道)。
但是:互联网上有大量证据表明上述代码将在现代编译器上产生 UB。例如,请参见此处和此处。
大多数情况下的论点是编译器可以自由考虑&a
并且f
不会相互混淆,因此可以自由地重新调度指令。
现在最大的问题是,这种编译器行为是否实际上是对标准的“过度解释”。
该标准唯一一次专门讨论“别名”是在 3.10.10 的脚注中,其中清楚地表明这些是管理别名的规则。
正如我之前提到的,我没有看到任何上述代码违反标准,但很多人(可能还有编译器人员)会认为它是非法的。
我真的很感激这里的一些澄清。
小更新:
正如成员 BenVoigt 正确指出的那样,在某些平台上int32_t
可能无法对齐,float
因此给定的代码可能违反了“存储足够对齐和大小”的规则。我想说的int32_t
是,在大多数平台上故意选择与此一致,float
并且此问题的假设是类型确实对齐。
小更新#2:
正如几位成员所指出的,这条线int32_t b = a;
可能违反了标准,尽管不是绝对肯定的。我同意这一观点,并且不改变问题的任何方面,请读者从我上面的声明中排除该行,即没有任何代码违反标准。