C语言 编译器将printf更改为put

hgb9j2n6  于 2023-02-07  发布在  其他
关注(0)|答案(2)|浏览(182)

请看下面的代码:

#include <stdio.h>

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

void bar() {
    printf("Hello world");
}

这两个函数生成的程序集为:

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

现在我知道了puts and printf之间的区别,但我发现gcc能够内省const char * 并判断是调用printf还是puts这一点非常有趣。
另一个有趣的事情是,在bar中,编译器将返回寄存器(eax)置零,尽管它是一个void函数。为什么它在那里而不是在foo中这样做?
我假设编译器"内省了我的字符串",或者对此有另一种解释,这对吗?

vohkndzv

vohkndzv1#

我假设编译器"内省了我的字符串",或者对此有另一种解释,这对吗?
是的,这就是所发生的事情。这是一个非常简单和常见的优化编译器所做的。
由于您的第一个printf()呼叫只是:

printf("Hello world\n");

它相当于:

puts("Hello world");

由于puts()不需要扫描和解析字符串中的格式说明符,因此它比printf()快得多,编译器会注意到字符串以换行符结尾,并且不包含格式说明符,因此会自动转换调用。
这也节省了一点空间,因为现在只需要将一个字符串"Hello world"存储在生成的二进制文件中。
请注意,对于以下形式的调用,这通常是不可能的:

printf(some_var);

如果some_var不是简单的常量字符串,编译器就无法知道它是否以\n结尾。
其他常见优化包括:

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

另一个有趣的事情是,在bar中,编译器将返回寄存器(eax)置零,尽管它是一个void函数。为什么它在那里而不是在foo中这样做?
参见此处:Why is %eax zeroed before a call to printf?

相关的有趣帖子

uujelgoq

uujelgoq2#

另一个有趣的事情是,在bar中,编译器清零了返回寄存器(eax),即使它是一个void函数,为什么它在那里而不是在foo中这样做呢?
这与标题中的问题完全无关,但仍然很有趣。
%eax的异或归零是在调用printf之前,因此是调用的一部分,与返回值无关。发生这种情况的原因是printf是一个varargs函数,而x86_64 ABI for varargs函数要求在xmm寄存器中传递浮点参数。并且要求在% al中传递此类参数的数量。因此,此指令用于确保%al为0,因为没有参数在xmm寄存器中传递给printf。
puts不是一个varargs函数,所以在这里不需要。

相关问题