严格的别名违规:为什么 gcc 和 clang 生成不同的输出?

Stricit aliasing violation: Why gcc and clang generate different output?

当类型转换违反 C 和 C++ 中严格的别名规则时,编译器可能会以传播错误常量值和允许未对齐访问的方式进行优化,从而导致性能下降或总线错误。

我写了一个简单的例子来查看当我违反 GCC 和 Clang 中的严格别名规则时编译器如何优化常量。

这是我得到的代码和说明。

#include <stdio.h>
#include <stdlib.h>

int
foo () //different result in C and C++
{
    int x = 1;
    long *fp = (long *)&x;
    *fp = 1234L;

    return x;
}

//int and long are not compatible 
//Wrong constant propagation as a result of strict aliasing violation
long
bar(int *ip, long *lp)
{
    *lp = 20L;
    *ip = 10;

    return *lp;
}

//char is always compatible with others
//constant is not propagated and memory is read
char
car(char *cp, long *lp)
{
    *cp = 'a';
    *lp = 10L;
    return *cp;
}

When I compile the code with the GCC 8.2 with -std=c11 -O3 option.

foo:
  movl 34, %eax
  ret
bar:
  movq , (%rsi)
  movl , %eax
  movl , (%rdi)
  ret
car:
  movb , (%rdi)
  movq , (%rsi)
  movzbl (%rdi), %eax
  ret

When I compile the code with the clang 7.0 with -std=c11 -O3 option.

foo: # @foo
  movl , %eax
  retq
bar: # @bar
  movq , (%rsi)
  movl , (%rdi)
  movl , %eax
  retq
car: # @car
  movb , (%rdi)
  movq , (%rsi)
  movb (%rdi), %al
  retq

bar 和 car 函数生成几乎相同的指令序列,并且 return 值在两种情况下都相同; bar 违反规则,常量被传播;并且 car 没有违反并且从内存中读取了正确的值。

但是,对于违反严格别名规则的foo函数,在GCC和Clang中生成不同的输出输出; gcc 传播存储在内存中的正确值(但不使用内存引用),而 clang 传播错误的值。似乎两个编译器都将常量传播作为其优化,但为什么两个编译器产生不同的结果呢?这是否意味着 GCC 会自动找出 foo 函数中的严格别名违规并传播正确的值?

为什么他们显示不同的指令流和结果?

Why can we say the bar doesn't violate the strict aliasing rule?

如果调用 bar 的代码不违反严格别名,bar 也不会违反严格别名。

举个例子

假设我们这样调用 bar:

int x;
long y;
bar(&x, &y);

严格别名要求两个不同类型的指针不指向同一个内存。 &x 和 &y 是不同的类型,它们指的是不同的内存。这并不违反严格的别名。

另一方面,假设我们这样称呼它:

long y;
bar((int *) &y, &y);

现在我们违反了严格的别名。但是,违规是来电者的错。