限制gcc中相同类型的两个对象之间的字段访问

ale*_*ius 5 c optimization alias gcc

假设我们有以下代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

当我们使用-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
Run Code Online (Sandbox Code Playgroud)

这意味着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);
}
Run Code Online (Sandbox Code Playgroud)

现在我在执行期间得到以下内容:

$ gcc-5.1.0 test1.c -O3 && ./a.out
0 9 0
$ gcc-5.1.0 test1.c -O0 && ./a.out
0 18 0
Run Code Online (Sandbox Code Playgroud)

那么您认为这段代码有效吗?因为如果它满足第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
Run Code Online (Sandbox Code Playgroud)

chq*_*lie 3

C11最新草案(N1570)第6.5节第7段内容如下:

\n\n

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

\n\n

我将其解释为意味着p和指向的对象q不能重叠,除非它们是同一个对象,因为这些t_str对象应该由正确的指针访问。

\n\n

该标准不够精确,无法明确表明不是指向由和之间共享的2 个对象组成的&p->f2有效指针。但这似乎是不正确的,因为编译器可能会在结构的末尾之间插入填充,或者实际上在结构的末尾之间插入填充。t_strintp[0]p[1]f1f2f2

\n\n

顺便说一句,&p->f2 - &p->f1不是一个有效的表达式,因为第 6.5.6 节加法运算符的第 9 段规定了此约束:当两个指针相减时,两个指针都应指向同一数组对象的元素,或者指向数组对象最后一个元素之后的元素;

\n\n

如果函数f()将指针作为char参数并通过该指针访问数据,则不能假设该数据与和所指向的结构的成员gcc不同。这种有点违反直觉的异常是为什么如此多的 C 库函数原型在许多指针参数上都有限定符的原因。(函数原型中的这些限定符只是对程序员的提示,但并没有真正告诉编译器任何东西)。intpqrestrict

\n