C给出基于优化级别(如新)不同的输出
在此基础上很好的博客文章,The Strict Aliasing Situation is Pretty Bad,我已经放置一段代码在线为您进行测试:C给出基于优化级别(如新)不同的输出
http://cpp.sh/9kht(输出之间的变化 - O0和-02)
#include <stdio.h>
long foo(int *x, long *y) {
*x = 0;
*y = 1;
return *x;
}
int main(void) {
long l;
printf("%ld\n", foo((int *)&l, &l));
}
是否有某种这里未定义行为?
当我们选择-O2水平时,内部发生了什么?
是的,这个计划已经未定义因为基于类型的别名规则,可以概括为“你无法通过类型的指针访问与A类声明的内存位置的行为, B,,除了当B是指向字符类型的指针(例如
unsigned char *
)。“这是一个近似值,但对于大多数目的而言它已经足够接近。请注意,当A是指向字符类型的指针,B可能而不是是别的 - 是的,这意味着通过uint32_t*
访问字节缓冲区“一次四个”的常见习惯是未定义的行为(博客帖子也涉及到这一点)。-
当编译
foo
时,编译器假定x
和y
可能不指向同一个对象。由此推断,通过*y
的写入不能改变*x
的值,并且它可以仅返回已知值*x
,0而不从内存中重新读取它。它只在打开优化时才会执行此操作,因为要跟踪每个指针可以指向哪些内容并且不能指向的开销很大(因此编译速度较慢)。请注意,这是一个“恶魔飞出你的鼻子”的局面:编译有权使生成的代码
foo
开始与cmp rx, ry beq __crash_the_program ...
(等UBSan一个工具可能做到这一点)
换言之,代码(int *)&l
表示将指针视为指向int的指针。它不会转换任何东西。因此,(int *)
告诉编译器允许您将long *传递给期望int *的函数。你在撒谎。在里面,foo希望x是一个指向int的指针,但它不是。内存布局不是它应该的。如你所见,结果是不可预测的。
另一方面,我不会使用l(ell)作为变量名称。它很容易与1(一)混淆。例如,这是什么?
int x = l;
这实际上是违反类型别名规则的一个很好的例子。我会把它作为我的规范副本。 – SergeyA