严格的别名违规:为什么 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);
现在我们违反了严格的别名。但是,违规是来电者的错。
当类型转换违反 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);
现在我们违反了严格的别名。但是,违规是来电者的错。