当类型转换违反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 $1234, %eax
ret
bar:
movq $20, (%rsi)
movl $20, %eax
movl $10, (%rdi)
ret
car:
movb $97, (%rdi)
movq $10, (%rsi)
movzbl (%rdi), %eax
ret
When I compile the code with the clang 7.0 with -std=c11 -O3 option.
foo: # @foo
movl $1, %eax
retq
bar: # @bar
movq $20, (%rsi)
movl $10, (%rdi)
movl $20, %eax
retq
car: # @car
movb $97, (%rdi)
movq $10, (%rsi)
movb (%rdi), %al
retq
bar和car函数生成几乎相同的指令序列,并且两种情况下的返回值都相同; bar 违反规则,并且传播常数;并且汽车没有违反,并且从内存中读取了正确的值。
但是,对于违反严格别名规则的foo函数,在GCC和Clang中生成不同的输出输出; gcc传播存储在内存中的正确值(但不随内存引用一起传播),而clang传播错误的值。似乎两个编译器都将常量传播作为其优化,但是为什么两个编译器生成不同的结果呢?这是否意味着GCC会在foo函数中自动找出严格的别名违规并传播正确的值?
为什么它们显示不同的指令流和结果?
答案 0 :(得分:1)
为什么我们要说酒吧不违反严格的别名规则?
如果调用bar的代码没有违反严格的别名,bar也不会违反严格的别名。
让我举个例子。
假设我们这样调用bar:
int x;
long y;
bar(&x, &y);
严格的别名要求不同类型的两个指针不能引用相同的内存。 &x和&y是不同的类型,它们引用不同的内存。这不违反严格的别名。
另一方面,假设我们这样称呼它:
long y;
bar((int *) &y, &y);
现在,我们违反了严格的别名。但是,违规是呼叫者的错。