在过去一周左右的时间里,我一直在阅读严格的别名规则,并遇到了这篇文章:Understanding C/C++ Strict Aliasing。
本文通过几种方式将两个交换32位整数的两半进行交换,给出了良好的示例和违反严格别名规则的示例。但是,我无法理解其中一个例子。
此代码被描述为已损坏。
uint32_t
swaphalves(uint32_t a)
{
a = (a >> 16) | (a << 16);
return a;
}
给出的理由是:
这个版本看起来很合理,但你不知道该版本的右侧和左侧是否合适 |将各自获得
a
的原始版本,或者如果其中一个获得结果 另一个。这里没有序列点,所以我们对顺序一无所知 这里的操作,您可能会从使用不同的相同编译器得到不同的结果 优化水平。
我不同意。这段代码对我来说很好看。 a
行中a = (a >> 16 | (a << 16);
只有一个写入,我希望在写入之前发生a
的两次读取。此外,没有指针或引用,也没有不兼容的类型。
我是否在此代码中错过了严格的别名冲突,或文章是否错误?
答案 0 :(得分:4)
此代码中的任何地方都没有指针和引用,因此严格的别名规则甚至不会进入图片。实际上,作者调用序列点而不是严格别名来证明它是未定义的断言。但是,似乎这种推理是错误的,并且代码片段具有完美定义的语义。正如Prasoon Saurav explains in more detail:
(§1.9/ 15)运算符操作数的值计算在运算符结果的值计算之前排序。
因此,对于=
运算符,a
和(a >> 16) | (a << 16)
的评估在分配之前进行了排序。这些都不成问题:虽然它们的部分相对于彼此都没有排序,但是对a
的写入仍然不需要按顺序排列。
(从技术上讲,这就提出了一个问题,即分配的副作用是如何按其计算值排序的,但是我找不到任何关于它的东西。大概它在标准的某个地方,但我没有副本方便。我强烈怀疑它在值计算之后按照下一段中的原因进行了排序。)
您还可以应用常识:写入a
需要首先评估(a >> 16) | (a << 16)
以写出正确的值,因此它不会发生在中间那个评价。该文章的另一个问题是即使
uint32_t
swaphalves(uint32_t a)
{
a = (a >> 16) | (a << 16);
return a;
}
由于序列点而导致未定义的行为,
uint32_t
swaphalves(uint32_t a)
{
return (a >> 16) | (a << 16);
}
不会(没有要编写的写入),因此占用本文其余部分的更复杂的版本(联合,memcpy)毫无意义。