1

我试图在 gdb 中查看一个简单 C 程序的反汇编二进制文件。

C程序:

int main(){
        int i = 2;
        if (i == 0){
                printf("YES, it's 0!\n");
        }else{
                printf("NO");
        }
        return 0;
}

反汇编指令:

   0x0000000100401080 <+0>:     push   rbp
   0x0000000100401081 <+1>:     mov    rbp,rsp
   0x0000000100401084 <+4>:     sub    rsp,0x30
   0x0000000100401088 <+8>:     call   0x1004010e0 <__main>
   0x000000010040108d <+13>:    mov    DWORD PTR [rbp-0x4],0x2
   0x0000000100401094 <+20>:    cmp    DWORD PTR [rbp-0x4],0x0
   0x0000000100401098 <+24>:    jne    0x1004010ab <main+43>
   0x000000010040109a <+26>:    lea    rax,[rip+0x1f5f]        # 0x100403000
   0x00000001004010a1 <+33>:    mov    rcx,rax
   0x00000001004010a4 <+36>:    call   0x100401100 <puts>
   0x00000001004010a9 <+41>:    jmp    0x1004010ba <main+58>
   0x00000001004010ab <+43>:    lea    rax,[rip+0x1f5b]        # 0x10040300d
   0x00000001004010b2 <+50>:    mov    rcx,rax
   0x00000001004010b5 <+53>:    call   0x1004010f0 <printf>
   0x00000001004010ba <+58>:    mov    eax,0x0
   0x00000001004010bf <+63>:    add    rsp,0x30
   0x00000001004010c3 <+67>:    pop    rbp
   0x00000001004010c4 <+68>:    ret
   0x00000001004010c5 <+69>:    nop

我想这个指令,

0x00000001004010a4 <+36>:    call   0x100401100 <puts>

指着

printf("YES, it's 0!\n");

现在让我们假设它是,那么我的疑问是为什么<push>在这里被调用,但<printf>被调用在0x00000001004010b5 <+53>: call 0x1004010f0 <printf>

4

2 回答 2

3

使用 C 标准中定义的语义,printf("YES, it's 0!\n")产生与 相同的输出puts("YES, it's 0!"),这可能更有效,因为不需要分析字符串以进行替换。

由于没有使用返回值,编译器可以用printf等效的调用替换调用puts

引入这种优化可能是为了减少经典 K&R 程序hello.c的可执行文件大小。printf用替换puts可以避免链接比 .printf大得多的代码puts。在您的情况下,这种优化会适得其反,因为两者puts都是printf链接的,但是现代系统使用动态链接,因此尝试以这种方式减小可执行文件大小不再有意义。

您可以在此Godbolt 编译器资源管理器页面上使用编译器设置来观察编译器行为:

  • 即使使用-O0gcc也会执行printf/puts替换,但 clang不会,并且两个编译器都会为这两个调用生成代码,而不是优化 test if (i == 0),这在禁用优化的情况下是可以的。我怀疑即使禁用了优化,gcc 团队也无法抗拒大小基准的偏差。

  • -O1以后,两个编译器都只为else分支生成代码,调用printf.

  • 如果将第二个字符串更改为 just "N",则将printf转换为对 的调用putchar,这是另一种优化。

于 2022-02-18T15:26:33.563 回答
1

这是一个优化。

printf使用没有格式说明符和尾随换行符的格式字符串调用等效于使用puts删除了尾随换行符的相同字符串进行调用。

由于printf处理格式说明符有很多逻辑,但puts只写给定的字符串,后者会更快。因此,在第一次调用printf编译器的情况下,会看到这种等价性并进行适当的替换。

于 2022-02-18T15:24:44.923 回答