限制gcc

时间:2015-05-22 18:07:34

标签: c gcc optimization alias

假设我们有以下代码:

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

2 个答案:

答案 0 :(得分:3)

C11最新草案(N1570)第6.5节第7段内写着:

对象的存储值只能由具有以下类型之一的左值表达式访问:88) - 与对象的有效类型兼容的类型, - 与对象的有效类型兼容的类型的限定版本, - 对应于对象的有效类型的有符号或无符号类型的类型, - 对应于对象有效类型的限定版本的有符号或无符号类型, - 聚合或联合类型,其成员中包含上述类型之一(包括递归地,子聚合或包含联合的成员),或者 - 字符类型。

我将此解释为pq指向的对象不能重叠,除非它们是同一个对象,因为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成员不同intp指向的结构。这个有点违反直觉的异常是为什么这么多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,我们也看不到增量被组合在一起,因为我们可以通过下溢获得不同的答案。