我相信C标准中的6.5p7定义了所谓的严格别名规则,具体如下:
一个对象只能通过其有效类型兼容的lvalue表达式访问其储存值,这里有效类型指对象最近一次存储操作的类型,具体规则如下:
- 与对象的有效类型兼容的类型
- 与对象的有效类型兼容的带限定符修饰的类型
- 与对象的有效类型相应的有符号或无符号整型类型
- 与对象的有效类型的带限定符修饰的类型相应的有符号或无符号整型类型
- 包含其中一种前述类型的聚合体或联合体类型(递归地包括子聚合体或内含联合体的成员)
- 字符类型
下面是一个简单例子展示了GCC基于该规则的假设进行的优化。
int IF(int *i, float *f) {
*i = -1;
*f = 0;
return *i;
}
IF:
mov DWORD PTR [rdi], -1
mov eax, -1
mov DWORD PTR [rsi], 0x00000000
ret
假设int
和float
不能别名,因此省略了对return *i
的负载。
接下来考虑第6种情况,它说明一个对象可以通过字符类型的左值表达式(char *
)被访问。
int IC(int *i, char *c) {
*i = -1;
*c = 0;
return *i;
}
IC:
mov DWORD PTR [rdi], -1
mov BYTE PTR [rsi], 0
mov eax, DWORD PTR [rdi]
ret
现在针对
return *i
有一个负载,因为根据规则,i
和c
可能重叠,*c = 0
可能会改变*i
中的内容。
那么我们是否也可以通过int *
修改char
?编译器是否应该考虑这种情况?
char CI(char *c, int *i) {
*c = -1;
*i = 0;
return *c;
}
CI: #GCC
mov BYTE PTR [rdi], -1
mov DWORD PTR [rsi], 0
movzx eax, BYTE PTR [rdi]
ret
CI: #Clang
mov byte ptr [rdi], -1
mov dword ptr [rsi], 0
mov al, byte ptr [rdi]
ret
查看汇编输出,GCC和Clang似乎都认为通过
int *
访问可以修改 char
。
也许重叠的意思很明显,即 A 与 B 重叠时 A 重叠 B,B 重叠 A。然而,我发现这个详细的答案中加粗强调了以下内容:
现在我感到非常困惑。答案还涉及到 GCC 向量类型,它具有请注意,像
char*
别名规则一样,may_alias
只能单向传递:使用int32_t*
读取__m256
是不安全的。甚至使用float*
读取__m256
也可能不安全,就像使用char buf[1024]; int *p = (int*)buf;
不安全一样。
may_alias
属性,因此可以像 char
一样别名。
至少,在以下示例中,GCC似乎认为重叠访问可以双向发生。int IV(int *i, __m128i *v) {
*i = -1;
*v = _mm_setzero_si128();
return *i;
}
__m128i VI(int *i, __m128i *v) {
*v = _mm_set1_epi32(-1);
*i = 0;
return *v;
}
IV:
pxor xmm0, xmm0
mov DWORD PTR [rdi], -1
movaps XMMWORD PTR [rsi], xmm0
mov eax, DWORD PTR [rdi]
ret
VI:
pcmpeqd xmm0, xmm0
movaps XMMWORD PTR [rsi], xmm0
mov DWORD PTR [rdi], 0
movdqa xmm0, XMMWORD PTR [rsi]
ret
https://godbolt.org/z/ab5EMx3bb
但是我可能漏掉了什么?strict aliasing 是否是单向的?
此外,在阅读当前的答案和评论后,我认为这段代码可能不符合标准。
typedef struct {int i;} S;
S s;
int *p = (int *)&s;
*p = 1;
请注意,
(int *)&s
与 &s.i
是不同的。 我目前的理解是,以 int 类型的左值表达式访问类型为 S 的对象,并且此情况未列在 6.5p7 中。
__m256i *对象*
的代码,比如GCC AVX _m256i强制转换为int数组导致值错误。但你正在使用一个__m128i *指针
来指向可以是不同底层类型的内存。请注意,你在我的答案中引用了一个char buf[1024]
的示例,这是一个字符数组对象,没有char*
参与其中。 (访问它可能涉及到char*
,因为buff[i]
的工作方式是*(buff+i)
,所以这种做法可能更安全,不像__m128i) - Peter Cordesint
成员是一个int
对象,但这与struct {int i;}
对象不同。通过*(int *)&s = 0;
,您正在通过int *
访问struct {int i;}
。不确定这是否可以接受。 - xiver77