【问题标题】:C gives different output based on optimization level (new example)C 根据优化级别给出不同的输出(新示例)
【发布时间】:2016-03-15 16:32:21
【问题描述】:

根据这篇非常好的博文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 级别时,内部发生了什么?

【问题讨论】:

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

标签: c gcc optimization compilation


【解决方案1】:
  1. 是的,这个程序有未定义的行为,因为基于类型的别名规则,可以概括为“你不能通过一个指针访问一个用类型A声明的内存位置type B, except 当 B 是一个字符类型的指针时(例如unsigned char *)。"这是一个近似值,但对于大多数用途来说已经足够接近了。请注意,当 A 是指向字符类型的指针时,B 可能 not 是别的东西——是的,这意味着访问字节缓冲区“一次四个" 通过uint32_t* 是未定义的行为(博文也涉及到这一点)。

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

    请注意,这是一种“恶魔飞出你的鼻子”的情况:编译器有权使为foo 生成的代码以

    cmp  rx, ry
    beq  __crash_the_program
    ...
    

    (像UBSan 这样的工具可能会做到这一点)

【讨论】:

  • 很好的答案@zwol,谢谢。可能想进一步扩展基于类型的别名规则? (比如它们在规范中的位置、其他示例等)
  • 其实charsigned charunsigned char三种不同的类型,可以访问任意类型。
  • @Dave5545 我会在我的计算机上有我的 C99 副本时这样做。
  • @zwol 如果您对此有任何进一步的信息,将不胜感激:)
【解决方案2】:

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

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

int x = l;

【讨论】:

    猜你喜欢
    • 2016-06-15
    • 2020-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-01
    • 1970-01-01
    相关资源
    最近更新 更多