C 编译器通过 运行 优化循环

C compiler optimize loop by running it

C 编译器能否通过 运行 优化循环?

例如:

int num[] = {1, 2, 3, 4, 5}, i;
for(i = 0; i < sizeof(num)/sizeof(num[0]); i++) {
  if(num[i] > 6) {
    printf("Error in data\n");
    exit(1);
  }
}

不是每次执行程序时都运行宁这个,编译器可以简单地运行这个并优化它吗?

您没有指定编译器,但是将 gcc 与 -O3 and 一起使用,在 for maybe 之外进行大小计算可能稍微调整一下。

是的。 C 编译器 unrolls loops automatically with options -O3 and -Otime.

编译器甚至可以做得更好。编译器不仅可以检查 运行 代码 "forward" 的效果,而且标准甚至允许它们在涉及潜在未定义行为的情况下 反向 工作代码逻辑。例如,给定:

#include <stdio.h>
int main(void)
{
  int ch = getchar();
  int q;
  if (ch == 'Z')
    q=5;
  printf("You typed %c and the magic value is %d", ch, q);
  return 0;
}

编译器有权假设程序永远不会接收任何输入,这会导致在 q 没有接收到值的情况下到达 printf;由于唯一会导致 q 接收值的输入字符是 'Z',因此编译器可以合法地将代码替换为:

int main(void)
{
  getchar();
  printf("You typed Z and the magic value is 5");
}

如果用户键入Z,原程序的行为将被明确定义,而后者的行为将与之匹配。如果用户键入任何其他内容,原始程序将调用未定义行为,因此,标准将不会对编译器可以做什么施加任何要求。编译器将有权做任何它喜欢的事情,包括产生与键入 Z.

产生的结果相同的结果。

让我们看看……(真的只能这样说了。)

拳头,我已将您的代码片段转换为我们可以实际尝试编译的内容,运行 并将其保存在名为 main.c.

的文件中
#include <stdio.h>

static int
f()
{
  const int num[] = {1, 2, 3, 4, 5};
  int i;
  for (i = 0; i < sizeof(num) / sizeof(num[0]); i++)
    {
      if (num[i] > 6)
        {
          printf("Error in data\n");
          return 1;
        }
    }
  return 0;
}

int
main()
{
  return f();
}

运行 gcc -S -O3 main.c 生成以下汇编文件(在 main.s 中)。

        .file   "main.c"
        .section        .text.unlikely,"ax",@progbits
.LCOLDB0:
        .section        .text.startup,"ax",@progbits
.LHOTB0:
        .p2align 4,,15
        .globl  main
        .type   main, @function
main:
.LFB22:
        .cfi_startproc
        xorl    %eax, %eax
        ret
        .cfi_endproc
.LFE22:
        .size   main, .-main
        .section        .text.unlikely
.LCOLDE0:
        .section        .text.startup
.LHOTE0:
        .ident  "GCC: (GNU) 5.1.0"
        .section        .note.GNU-stack,"",@progbits

即使您不懂汇编,您也会注意到字符串 "Error in data\n" 不存在于文件中,因此显然一定进行了某种优化。

如果我们仔细观察为 main 函数生成的机器指令,

xorl    %eax, %eax
ret

我们可以看到它所做的只是将 EAX 寄存器与自身进行异或运算(结果始终为零)并将该值写入 EAX。然后它再次 returns。 EAX 寄存器用于保存 return 值。正如我们所见,f 函数被完全优化掉了。