编译器将 printf 更改为 puts

skg*_*nga 4 c assembly gcc compiler-optimization

考虑以下代码:

#include <stdio.h>

void foo() {
    printf("Hello world\n");
}

void bar() {
    printf("Hello world");
}
Run Code Online (Sandbox Code Playgroud)

这两个函数产生的程序集是:

.LC0:
        .string "Hello world"
foo():
        mov     edi, OFFSET FLAT:.LC0
        jmp     puts
bar():
        mov     edi, OFFSET FLAT:.LC0
        xor     eax, eax
        jmp     printf

Run Code Online (Sandbox Code Playgroud)

现在我知道puts 和 printf之间的区别,但我发现这很有趣,因为 gcc 能够内省 const char* 并确定是调用 printf 还是 puts。

另一个有趣的事情是bar,编译器将返回寄存器 ( eax)清零,即使它是一个void函数。为什么它在那里而不是在里面foo

我假设编译器“内省了我的字符串”是否正确,或者对此有另一种解释?

Mar*_*lli 8

我假设编译器“内省了我的字符串”是否正确,或者对此有另一种解释?

是的,这正是发生的事情。这是由编译器完成的非常简单和常见的优化。

由于您的第一个printf()电话只是:

printf("Hello world\n");
Run Code Online (Sandbox Code Playgroud)

它相当于:

puts("Hello world");
Run Code Online (Sandbox Code Playgroud)

由于puts()不需要扫描和解析格式说明符的字符串,因此它比printf(). 编译器注意到您的字符串以换行符结尾并且不包含格式说明符,因此会自动转换调用。

这也节省了一点空间,因为现在只"Hello world"需要在生成的二进制文件中存储一个字符串。

请注意,对于以下形式的调用,这通常是不可能的:

printf(some_var);
Run Code Online (Sandbox Code Playgroud)

如果some_var不是简单的常量字符串,编译器无法知道它是否以\n.

其他常见的优化有:

  • strlen("constant string") 可能在编译时被评估并转换为数字。
  • memmove(location1, location2, sz)memcpy()如果编译器确定location1并且location2不重叠,则可能会被转换为。
  • memcpy()可以在单个mov指令中转换小尺寸,即使尺寸更大,有时也可以内联调用更快。

另一个有趣的事情是bar,编译器将返回寄存器 ( eax)清零,即使它是一个void函数。为什么它在那里而不是在里面foo

请参阅此处:为什么 %eax 在调用 printf 之前归零?


相关有趣的帖子

  • @MarcoBonelli:有趣的事实:编译器还知道如何将 `printf("%s\n", string)` 优化为 put。其机制是“printf”被视为内置函数。 (2认同)
  • 相关链接,不完全重复:[Why does GCC optimization this call to printf?](//stackoverflow.com/q/37435984) 有关 GCC 可以/不能做什么的更多详细信息,以及文章的链接关于它。[可以在 C 程序中自动将 printf 替换为 put 吗?](//stackoverflow.com/q/25816659) 是此方法的一个变体。[为什么无论我使用 printf 还是 puts,当我反汇编时它都会显示 puts?](//stackoverflow.com/q/41371002) 是这个的重复,答案不太详细,关闭它。另外[printf@plt和puts@plt之间的区别](//stackoverflow.com/q/39007002) (2认同)

Chr*_*odd 6

另一个有趣的事情是,在 bar 中,编译器将返回寄存器 (eax) 清零,即使它是一个 void 函数。为什么它在那里而不是在 foo 中这样做?

这与标题中的问题完全无关,但仍然很有趣。

异或清零%eax是在调用 printf之前,因此是调用的一部分,与返回值无关。发生这种情况的原因是它printf是一个 varargs 函数,并且 varargs 函数的 x86_64 ABI 需要在 xmm 寄存器中传递浮点参数,并且需要在 %al 中传递此类参数的数量。因此,这条指令的作用是确保 %al 为 0,因为没有参数在 xmm 寄存器中传递给 printf。

put 不是可变参数函数,因此不需要它。