假设我们有以下代码:
typedef struct {
int f1;
int f2;
} t_str;
int f(t_str* p, t_str* q)
{
p[0].f1++;
q[0].f2++;
p[0].f1++;
q[0].f2++;
p[0].f1++;
q[0].f2++;
p[0].f1++;
q[0].f2++;
p[0].f1++;
q[0].f2++;
p[0].f1++;
q[0].f2++;
p[0].f1++;
q[0].f2++;
p[0].f1++;
q[0].f2++;
p[0].f1++;
q[0].f2++;
return 0;
}
当我们使用-O3选项编译它(我使用gcc-5.1.0)时,编译器获得以下汇编程序:
f:
.LFB0:
.cfi_startproc
movl 8(%esp), %edx
movl 4(%esp), %ecx
movl 4(%edx), %eax
addl $9, (%ecx)
addl $9, %eax
movl %eax, 4(%edx)
xorl %eax, %eax
ret
.cfi_endproc
这意味着gcc决定访问p的字段f1并且访问q的字段f2永远不会别名。我想这来自于假设两个相同类型的对象从不重叠或它们是相同的。但我没有在标准中找到问题。
所以,请问,任何人都可以在标准中找到这个问题,或者为什么gcc限制字段访问,或评论发生了什么?
UPD:
好吧,我也考虑了第6.5节的第7段,但对于我来说,以明确的形式为所有对象提供类似的东西会更舒服:
6.5.16.1简单分配
3如果从另一个对象读取存储在对象中的值 以任何方式重叠第一个对象的存储,然后是 重叠应准确,两个对象应具有合格或 兼容类型的不合格版本;否则,行为是 未定义。
不幸的是,这条规则不能在这里使用。
现在看,如果对于上面的代码我做了以下功能:
void main()
{
char * c = malloc(12);
memset(c, 0, 12);
f((t_str *)(c + 4), (t_str *)c);
printf("%d %d %d\n", ((t_str *)c)->f1, ((t_str *)c)->f2, ((t_str *)(c + 4))->f2);
}
现在我在执行期间得到以下内容:
$ gcc-5.1.0 test1.c -O3 && ./a.out
0 9 0
$ gcc-5.1.0 test1.c -O0 && ./a.out
0 18 0
那你认为这段代码有效吗?因为如果它满足第6.5节的第7段,我就不会感到舒服。
PS:有趣的是:$ gcc-5.1.0 test1.c -O3 -fwhole-program && ./a.out
0 10 0
$ gcc-5.1.0 test1.c -O3 -flto && ./a.out
0 10 0
答案 0 :(得分:3)
C11最新草案(N1570)第6.5节第7段内写着:
对象的存储值只能由具有以下类型之一的左值表达式访问:88) - 与对象的有效类型兼容的类型, - 与对象的有效类型兼容的类型的限定版本, - 对应于对象的有效类型的有符号或无符号类型的类型, - 对应于对象有效类型的限定版本的有符号或无符号类型, - 聚合或联合类型,其成员中包含上述类型之一(包括递归地,子聚合或包含联合的成员),或者 - 字符类型。
我将此解释为p
和q
指向的对象不能重叠,除非它们是同一个对象,因为t_str
对象应该由适当的指针访问。
标准不够准确,无法明确指出&p->f2
不是t_str
对象的有效指针,该对象由int
和{{{{}}之间共享的2 p[0]
组成1}}。但它似乎不正确,因为编译器可能在p[1]
和f1
之间或实际上在f2
和结构的末尾之间插入填充。
顺便说一句,f2
不是有效的表达式,因为6.5.6 Additive运算符的第9段说明了这个约束:当减去两个指针时,两个指针都指向同一个数组对象的元素,或者一个超过数组对象的最后一个元素;
如果函数&p->f2 - &p->f1
将指针f()
作为参数并通过此指针访问数据,则char
无法假设此数据与gcc
成员不同int
和p
指向的结构。这个有点违反直觉的异常是为什么这么多C库函数原型在许多指针参数上都有q
限定符的原因。 (函数原型中的这些限定符仅仅是程序员的一个提示,但并不能真正告诉编译器。)
答案 1 :(得分:0)
正如chqrlie所提到的,gcc不太可能让你创建&p[0].f1 == &q[0].f2
的情况。话虽这么说,但它不应该有所作为。这些操作在数学上是通勤的。并且在代码中分解的唯一方法是如果存在整数溢出,那么再次顺序并不重要。我对此进行了测试,对于我的编译器(gcc 3.4.6),只要我打开任何优化,就会出现此优化。
作为对应点,考虑当我们将结构更改为
时会发生什么typedef struct {
double f1;
double f2;
} t_str;
在这种情况下,即使使用-O3,我们也看不到增量被组合在一起,因为我们可以通过下溢获得不同的答案。