c - 禁用优化的 c alloca 函数的奇怪汇编代码 - gcc 按常量 16 使用 DIV 和 IMUL,并进行移位?

标签 c gcc x86 compiler-optimization alloca

我有这个简单的 c 代码

#include <stdio.h>
#include <alloca.h>

int main()
{
    char* buffer = (char*)alloca(600);
    snprintf(buffer, 600, "Hello %d %d %d\n", 1, 2, 3);

    return 0;
}

我希望为 alloca 函数生成的汇编代码只会递减堆栈指针(一个子指令),并且可能会进行一些对齐(一和指令),但生成的汇编代码非常复杂,甚至比你的效率低​​'预计。

这是 objdump -d main.o 的输出,基于 gcc -c 的输出(没有优化,所以默认的 -O0)

    0000000000400596 <main>:
  400596:   55                      push   %rbp
  400597:   48 89 e5                mov    %rsp,%rbp
  40059a:   48 83 ec 10             sub    $0x10,%rsp
  40059e:   b8 10 00 00 00          mov    $0x10,%eax
  4005a3:   48 83 e8 01             sub    $0x1,%rax
  4005a7:   48 05 60 02 00 00       add    $0x260,%rax
  4005ad:   b9 10 00 00 00          mov    $0x10,%ecx
  4005b2:   ba 00 00 00 00          mov    $0x0,%edx
  4005b7:   48 f7 f1                div    %rcx
  4005ba:   48 6b c0 10             imul   $0x10,%rax,%rax
  4005be:   48 29 c4                sub    %rax,%rsp
  4005c1:   48 89 e0                mov    %rsp,%rax
  4005c4:   48 83 c0 0f             add    $0xf,%rax
  4005c8:   48 c1 e8 04             shr    $0x4,%rax
  4005cc:   48 c1 e0 04             shl    $0x4,%rax
  4005d0:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  4005d4:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  4005d8:   41 b9 03 00 00 00       mov    $0x3,%r9d
  4005de:   41 b8 02 00 00 00       mov    $0x2,%r8d
  4005e4:   b9 01 00 00 00          mov    $0x1,%ecx
  4005e9:   ba a8 06 40 00          mov    $0x4006a8,%edx
  4005ee:   be 58 02 00 00          mov    $0x258,%esi
  4005f3:   48 89 c7                mov    %rax,%rdi
  4005f6:   b8 00 00 00 00          mov    $0x0,%eax
  4005fb:   e8 a0 fe ff ff          callq  4004a0 <snprintf@plt>
  400600:   b8 00 00 00 00          mov    $0x0,%eax
  400605:   c9                      leaveq 
  400606:   c3                      retq   
  400607:   66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  40060e:   00 00 

知道生成的汇编代码的目的是什么吗?我正在使用 gcc 8.3.1。

最佳答案

当然,通常的 Debug模式/反优化行为是将每个 C 语句编译为单独的 block ,并且非 register变量实际上在内存中。 (Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?)。

但是,是的,这超出了“未优化”的范围。没有一个理智的人会期望 GCC 的固定指令序列(或 GIMPLE 或 RTL 逻辑,无论它扩展到什么阶段) alloca涉及 div 的逻辑通过编译时常数 2 的幂,而不是移位或只是 AND。 x /= 16;不会编译为 div如果你自己用 C 源代码编写,即使使用 gcc -O0 .

通常,GCC 会尽可能多地对常量表达式进行编译时求值,例如 x = 5 * 6不会在运行时使用imul。但它扩展其alloca的点逻辑必须在该点之后,可能很晚(在大多数其他传递之后)来解释所有这些错过的优化。因此,它不会受益于在 C 源逻辑上运行的相同 channel 。

它做了两件事:

  • 将分配大小向上舍入(将其放入寄存器后的常量 600)将其舍入为 16 的倍数,方法是: ((16ULL - 1) + x) / 16 * 16 。一个理智的编译器至少会使用右移/左移,如果不优化到 (x+15) & -16 。但不幸的是 GCC 使用 divimul到 16,即使它是 2 的恒定幂。

  • 将分配空间的最终地址舍入为 16 的倍数(尽管这已经是因为 RSP 开始 16 字节对齐并且分配大小已向上舍入。)这与 ((p+15) >> 4) << 4它比 div/imul 效率高得多(特别是对于 Ice Lake 之前的 Intel 上的 64 位操作数大小),但仍然比 and $-16, %rax 效率低。 。当然,做那些已经毫无意义的工作是愚蠢的。

那么当然它必须将指针存储到 char* buffer 中.

在下一条语句的 asm block 中,将其重新加载为 sprintf 的参数(低效地进入 RAX 而不是直接进入 RDI,通常为 gcc -O0 ),以及设置寄存器参数。


所以这很糟糕,但是通过 alloca 的预设逻辑的后期扩展来解释是非常合理的。 ,在大多数转换(“优化”)过程已经运行之后。请注意-O0 doesn't literally mean "no optimization" ,它只是意味着“快速编译,并提供一致的调试”。


相关:

关于c - 禁用优化的 c alloca 函数的奇怪汇编代码 - gcc 按常量 16 使用 DIV 和 IMUL,并进行移位?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66289556/

相关文章:

C++11 标准与 CUDA 6.0

c++ - 在 C 项目中使用 C++ 库会导致一长串错误

c - 函数指针局部变量的意外值

c++ - 为什么默认构造函数和空构造函数在 C++ 中生成不同的机器代码?

c++ - union 的平等比较?

c - ftell 函数在 C 中为标准输出提供 -1

c - 为什么不能根据它指向的变量类型推断指针类型?

c - 以下内联汇编代码有什么问题?

c++ - 将内联汇编与序列化说明一起使用

c - 数组值无故改变