基于这篇非常好的博客文章The Strict Aliasing Situation is Pretty Bad,我已经在网上发布了一段代码供您测试:
http://cpp.sh/9kht(输出在-O0和-O2之间变化)
#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级别时,内部会发生什么?
答案 0 :(得分:12)
是的,此程序具有未定义的行为,因为基于类型的别名规则,可以概括为&#34;您无法访问通过类型A声明的内存位置当B是指向字符类型的指针(例如unsigned char *
)时,类型为B的指针除之外。&#34;这是近似值,但它足够接近大多数用途。请注意,当 A 是指向字符类型的指针时,B可能不是别的 - 是的,这意味着访问字节缓冲区的常用习语&#34;四在一个时间&#34;通过uint32_t*
是未定义的行为(博客文章也涉及到这一点)。
编译器在编译foo
时假定x
和y
可能不指向同一个对象。从中可以看出,写入*y
不能更改*x
的值,它只能返回*x
,0的已知值,而无需从内存中重新读取它。它只在打开优化时执行此操作,因为跟踪每个指针可以指向和不能指向的内容是昂贵的(因此编译速度较慢)。
请注意,这是一个&#34;恶魔飞出你的鼻子&#34;情况:编译器有权使foo
的生成代码以
cmp rx, ry
beq __crash_the_program
...
(以及像UBSan这样的工具可能会这样做)
答案 1 :(得分:1)
换句话说,代码(int *)&l
表示将指针视为指向int的指针。它没有转换任何东西。因此,(int *)
告诉编译器允许您将long *传递给期望int *的函数。你骗了它。在里面,foo期望x是指向int的指针,但事实并非如此。内存布局不是应该的。如你所见,结果是不可预测的。
另一方面,我不会使用l(ell)作为变量名。它太容易与1(一)混淆。例如,这是什么?
int x = l;