请看下面的代码:
#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
中这样做?
我假设编译器"内省了我的字符串",或者对此有另一种解释,这对吗?
2条答案
按热度按时间vohkndzv1#
我假设编译器"内省了我的字符串",或者对此有另一种解释,这对吗?
是的,这就是所发生的事情。这是一个非常简单和常见的优化编译器所做的。
由于您的第一个
printf()
呼叫只是:它相当于:
由于
puts()
不需要扫描和解析字符串中的格式说明符,因此它比printf()
快得多,编译器会注意到字符串以换行符结尾,并且不包含格式说明符,因此会自动转换调用。这也节省了一点空间,因为现在只需要将一个字符串
"Hello world"
存储在生成的二进制文件中。请注意,对于以下形式的调用,这通常是不可能的:
如果
some_var
不是简单的常量字符串,编译器就无法知道它是否以\n
结尾。其他常见优化包括:
strlen("constant string")
可能会在编译时求值并转换为数字。location1
和location2
不重叠,则memmove(location1, location2, sz)
可能会转换为memcpy()
。mov
指令中转换小尺寸的memcpy()
,并且即使尺寸较大,有时也可以内联调用以更快。另一个有趣的事情是,在
bar
中,编译器将返回寄存器(eax
)置零,尽管它是一个void
函数。为什么它在那里而不是在foo
中这样做?参见此处:Why is %eax zeroed before a call to printf?
相关的有趣帖子
uujelgoq2#
另一个有趣的事情是,在bar中,编译器清零了返回寄存器(eax),即使它是一个void函数,为什么它在那里而不是在foo中这样做呢?
这与标题中的问题完全无关,但仍然很有趣。
%eax
的异或归零是在调用printf之前,因此是调用的一部分,与返回值无关。发生这种情况的原因是printf
是一个varargs函数,而x86_64 ABI for varargs函数要求在xmm寄存器中传递浮点参数。并且要求在% al中传递此类参数的数量。因此,此指令用于确保%al为0,因为没有参数在xmm寄存器中传递给printf。puts不是一个varargs函数,所以在这里不需要。