使用GCC

时间:2018-09-23 06:00:09

标签: c

我已经将简单定义与计算定义进行了比较,汇编代码使我感到惊讶。

#define HOURS 365*24#define HOURS 8760

然后主要是int a = HOURS

它们两者都生成看起来完全相同的汇编代码。这在gcc(版本7.3.0,64位体系结构)和avr-gcc(版本5.4.0,8位体系结构)中都适用。为什么会这样?据我了解,define只是一个预处理器,它将在编译之前替换文件中的确切字符串。如果是这样,为什么在运行时不进行计算?看到常量,这是gcc的把戏吗?

1 个答案:

答案 0 :(得分:3)

任何好的编译器都会在编译时评估简单表达式。您看到的行为与预处理无关。如果您直接写:

int a = 365 * 24;

然后,任何好的编译器都会生成将a初始化为8760的代码。(某些编译器如果在禁用优化功能的情况下执行,则可能不会。但是,当启用优化时,任何体面的编译器都会执行此优化。)< / p>

在现代编译器中,优化的作用远不止于此。例如,它们将在循环外标识不变代码。 (也就是说,如果循环内有表达式或什至是完整语句,其结果不取决于当前循环迭代中的数据,则它们会将这些表达式或语句移到循环外,因此它们只被求值一次循环执行的时间间隔。)它们将删除从未使用过的对象。如果条件分支的一个路径在某些情况下具有未定义的行为,则它们可能会推断出该路径永远不会被采用,因此他们将在那些情况下通过删除该路径和条件分支来优化代码。他们甚至可以用等效的数学表达式代替整个循环。例如,当我编译时:

#include <stdio.h>
#include <stdlib.h>


int main(int argc, char *argv[])
{
    int n = atoi(argv[1]);
    int s = 0;
    for (int i = 1; i <= n; ++i)
        s += i;
    printf("%d\n", s);
}

对于Apple LLVM 9.1.0(clang-902.0.39.2),生成的汇编代码不包含循环。调用atoi后,它执行实际上是 1 printf("%d\n", 0 < n ? n*(n+1)/2 : 0);指令。因此,该循环完全由测试和简单的算术表达式代替。

脚注

1 实际说明是:

leal    -1(%rax), %ecx
movl    %eax, %edx
addl    $-2, %edx
imulq   %rcx, %rdx
shrq    %rdx
leal    -1(%rdx,%rax,2), %esi

更接近的表示法是(n-1)*(n-2)/2 + 2*n - 1。编译器执行此操作的原因之一是,如果nINT_MAX,它将得到“正确”的结果,因为n*(n+1)/2会产生零,因为n+1换行,而{{ 1}}得到的结果与循环得到的结果相同(假定为包装算术),由于包装,结果为(n-1)*(n-2)/2 + 2*n - 1INT_MAX+1。根据C规则,编译器可以使用更简单的INT_MIN,因为溢出的行为不是由C定义的,但是此编译器可能符合更严格的规则,例如使用包装二进制补码算法。