2016-03-15 42 views
6

在此基础上很好的博客文章,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水平时,内部发生了什么?

+1

这实际上是违反类型别名规则的一个很好的例子。我会把它作为我的规范副本。 – SergeyA

回答

12
  1. 是的,这个计划已经未定义因为基于类型的别名规则,可以概括为“你无法通过类型的指针访问与A类声明的内存位置的行为, B,,除了当B是指向字符类型的指针(例如unsigned char *)。“这是一个近似值,但对于大多数目的而言它已经足够接近。请注意,当A是指向字符类型的指针,B可能而不是是别的 - 是的,这意味着通过uint32_t*访问字节缓冲区“一次四个”的常见习惯是未定义的行为(博客帖子也涉及到这一点)。

  2. 当编译foo时,编译器假定xy可能不指向同一个对象。由此推断,通过*y的写入不能改变*x的值,并且它可以仅返回已知值*x,0而不从内存中重新读取它。它只在打开优化时才会执行此操作,因为要跟踪每个指针可以指向哪些内容并且不能指向的开销很大(因此编译速度较慢)。

    请注意,这是一个“恶魔飞出你的鼻子”的局面:编译有权使生成的代码foo开始与

    cmp rx, ry 
    beq __crash_the_program 
    ... 
    

    (等UBSan一个工具可能做到这一点)

+0

非常好的回答@zwol,谢谢。可能想要扩展一些基于类型的别名规则? (比如他们在spec中的位置,其他例子等) – Dave5545

+2

其实,char,signed char和unsigned char是允许访问任意类型的三种不同类型。 – EOF

+0

@ Dave5545当我在装有我的C99副本的计算机上时,我会这样做。 – zwol

1

换言之,代码(int *)&l表示将指针视为指向int的指针。它不会转换任何东西。因此,(int *)告诉编译器允许您将long *传递给期望int *的函数。你在撒谎。在里面,foo希望x是一个指向int的指针,但它不是。内存布局不是它应该的。如你所见,结果是不可预测的。

另一方面,我不会使用l(ell)作为变量名称。它很容易与1(一)混淆。例如,这是什么?

int x = l;