我们最近在大学举办了一场关于多种语言编程专题的讲座。
讲师写下了以下函数:
inline u64 Swap_64(u64 x)
{
u64 tmp;
(*(u32*)&tmp) = Swap_32(*(((u32*)&x)+1));
(*(((u32*)&tmp)+1)) = Swap_32(*(u32*) &x);
return tmp;
}
虽然我完全理解这在可读性方面也是非常糟糕的风格,但他的主要观点是这部分代码在生产代码中运行良好,直到它们启用了高优化级别。然后,代码什么也不做。
他说所有对变量tmp
的赋值都会被编译器优化掉。但是为什么会这样呢?
我了解在某些情况下需要声明变量 volatile 以便编译器不会触及它们,即使他认为它们从未被读取或写入但我不知道为什么这会发生在这里。
最佳答案
此代码违反了 strict aliasing rules这使得通过不同类型的指针访问 object 是非法的,尽管允许通过 *char ** 访问。允许编译器假设不同类型的指针不指向同一个内存并进行相应的优化。这也意味着代码调用 undefined behavior并且真的可以做任何事情。
此主题的最佳引用之一是 Understanding Strict Aliasing我们可以看到第一个示例与 OP 的代码类似:
uint32_t swap_words( uint32_t arg )
{
uint16_t* const sp = (uint16_t*)&arg;
uint16_t hi = sp[0];
uint16_t lo = sp[1];
sp[1] = hi;
sp[0] = lo;
return (arg);
}
文章解释了这段代码违反了严格的别名规则,因为 sp
是 arg
的别名,但它们有不同的类型,并说虽然它将编译,很可能 arg
在 swap_words
返回后将保持不变。尽管通过简单的测试,我无法使用上面的代码或 OPs 代码重现该结果,但这并不意味着什么,因为这是未定义的行为,因此不可预测。
文章继续讨论了许多不同的案例,并提出了几种可行的解决方案,包括通过 union 类型双关,这在C99中得到了很好的定义 1 并且可能在 C++ 中未定义,但实际上大多数主要编译器都支持,例如这里是 gcc's reference on type-punning .上一个帖子Purpose of Unions in C and C++进入血淋淋的细节。虽然这个主题有很多线程,但这似乎做得最好。
该解决方案的代码如下:
typedef union
{
uint32_t u32;
uint16_t u16[2];
} U32;
uint32_t swap_words( uint32_t arg )
{
U32 in;
uint16_t lo;
uint16_t hi;
in.u32 = arg;
hi = in.u16[0];
lo = in.u16[1];
in.u16[0] = lo;
in.u16[1] = hi;
return (in.u32);
}
引用 C99 draft standard 中的相关部分关于严格别名是6.5
Expressions段落7说:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:76)
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the object,
— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
— a character type.
和 脚注 76 说:
The intent of this list is to specify those circumstances in which an object may or may not be aliased.
以及来自 C++ draft standard 的相关部分是3.10
左值和右值 段落10
文章Type-punning and strict-aliasing对该主题和 C99 revisited 进行了较为温和但不完整的介绍对C99和别名进行了深入的分析,不是轻读。这个回答 Accessing inactive union member - undefined?通过 C++ 中的 union 来回顾类型双关的困惑细节,也不是轻松阅读。
脚注:
- 报价 comment由 Pascal Cuoq:[...]C99 最初措辞笨拙,似乎使通过 union 的类型双关语未定义。实际上,尽管 union 的类型双关在 C89 中是合法的,在 C11 中是合法的,并且在 C99 中一直是合法的,尽管直到 2004 年委员会才修复了不正确的措辞,并随后发布了 TC3。 open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm
关于c++ - 为什么优化会杀死这个功能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20922609/