处理数组访问时可能产生不符合代码的代码

时间:2014-04-26 06:29:33

标签: c++ c arrays pointers

用于遵循简单的代表性代码

int foo()
{
    extern int i;
    extern int a[];
    int sum = 0;
    sum += a[i + 10];
    sum += a[i + 20];

    return sum;
}

LLVM生成的代码如

movl    a+40(,%eax,4), %eax
...
movl    a+80(,%eax,4), %eax

此代码是否真的符合C / C ++标准?

不应该生成代码计算a + (40 + eax * 4)而不是(a + 40) + (eax * 4)给定C / C ++只有在计算出的地址落在同一个数组对象中时才定义地址算法?

在上面的情况下,可能会发生a + 40落在数组之外,但a + (40 + eax * 4)仍然可以在同一个数组中(如果" i"保持负值)。

4 个答案:

答案 0 :(得分:4)

C ++标准定义了C ++程序的行为。这种行为由编译器以他们选择的任何方式实现。

仅指向单个数组的指针限制仅适用于C ++代码;对于编译器将C ++翻译成的语言,它可能有也可能不成立,但即使这样做,这个限制也是由新的(通常是汇编语言)语言定义的,而不是由C ++定义的。

答案 1 :(得分:1)

汇编代码没有C / C ++标准,所以你问的问题是不连贯的。就C / C ++标准而言,唯一重要的是,如果C / C ++源代码严格符合这些标准,汇编代码是否会产生标准所要求的行为。如果你相信它不会,你就无法解释为什么你相信它。

答案 2 :(得分:0)

  

只有当计算出的地址落在同一个数组对象中时,C / C ++才定义地址算法?

那是真的。这意味着如果您在a[]的范围之外访问,则行为未定义。

我不知道你如何从中得出结论&#34;代码不符合&#34;。如果a的大小合适,a[i+10]a[i+20]不会超出a范围,则代码会符合,否则它不会<\ n < / p>

如果那些超出a的范围,那么行为是未定义的,这意味着你不能抱怨编译器在这种情况下发出的代码。

  

在上面的情况下,可能会发生+ 40落在阵列外但是+(40 + eax * 4)仍然可以在同一个阵列中(如果&#34; i&#34;保持负值)。

这并不重要,行为未定义。

答案 3 :(得分:0)

正如其他人所说,标准中没有任何内容可以准确说明编译器应该做什么,只要它产生正确的结果即可。

在您的情况下,它通过将a+40变为常量值来优化计算(一旦将代码转换为机器代码,它将是常量),而不是使用更长的指令形式或更糟糕的使用第二个寄存器只是为了添加常数40.如果i是负数,它将从地址中减去。无论您将其计算为(a+40) + -5*4还是a + (40 + -5*4)都不会产生任何影响。

请注意,如果您使用64位模式,编译器将生成代码以首先对索引进行签名扩展,例如:

movl    i(%rip), %eax
leal    10(%rax), %ecx
addl    $20, %eax
movslq  %eax, %rdx
movslq  %ecx, %rcx
movl    a(,%rcx,4), %eax
addl    a(,%rdx,4), %eax
ret

如果我们将i更改为unsigned,则会有所不同:

movl    i(%rip), %eax
leal    20(%rax), %edx
leal    10(%rax), %ecx
movl    a(,%rcx,4), %eax
addl    a(,%rdx,4), %eax
ret

请注意额外的movslq指令,该指令将32位int值符号扩展为64位值。

(我无法生成与示例中显示的完全相同的指令集,但我没有尝试所有选项组合,并且您没有说明您使用的是哪个版本的clang - 我我只是猜测你正在使用32位模式,因为这似乎比我的结果更好地匹配64位结果。如果你发布的代码来自64位编译器,那几乎肯定是一个错误)