在处理数组访问时可能会生成不一致的代码

Possibly non-conformant code generation when dealing with array accesses

本文关键字:不一致 代码 数组 处理 访问      更新时间:2023-10-16

对于以下简单的代表代码

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++标准吗?

在给定C/C++的情况下,生成的代码不应该计算a + (40 + eax * 4)而不是(a + 40) + (eax * 4)吗?仅当计算的地址位于同一数组对象中时,才定义地址算术?

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

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

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

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

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

这是真的。这意味着如果访问超出a[]的范围,则行为是未定义的。

我不知道你是如何从中得出"代码不一致"的结论的。如果证明a是正确的大小,使得a[i+10]a[i+20]不超出a的范围,则代码符合,否则它不会

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

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

没关系,行为是不明确的。

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

在您的情况下,它通过将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位编译器,那么几乎可以肯定这是一个错误)