C 编译成汇编时的 put() 和 printf() 有什么区别?

问题描述 投票:0回答:2

这是我使用

puts()
的 C 程序:

#include <stdio.h>
int main(void){
  puts("testing");
}

使用

gcc -S -o sample.s sample.c
将其编译成Assembly后,这就是我得到的:

        .file   "sample.c"
        .section        .rodata
.LC0:
        .string "testing"
        .text
.globl main
        .type   main, @function
main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $16, %esp
        movl    $.LC0, (%esp)
        call    puts
        leave
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 4.4.5 20110214 (Red Hat 4.4.5-6)"
        .section        .note.GNU-stack,"",@progbits

我以同样的方式重复了一遍,但这次我使用

printf()
而不是
puts()
,这就是我得到的:

    .file   "sample.c"
        .section        .rodata
.LC0:
        .string "testing"
        .text
.globl main
        .type   main, @function
main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $16, %esp
        movl    $.LC0, %eax        //this is the difference
        movl    %eax, (%esp)
        call    printf
        leave
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 4.4.5 20110214 (Red Hat 4.4.5-6)"
        .section        .note.GNU-stack,"",@progbits

这是我不明白的地方:在

printf()
版本中,他们
mov $.LC0
%eax
,然后是
mov %eax
(%esp)
,但在
puts()
版本中,他们
mov %.LC0
直接到
(%esp) 
.
我不知道为什么。

c assembly gcc x86
2个回答
8
投票

在汇编级别,这两个函数之间的最大区别在于

puts()
函数仅采用一个参数(指向要显示的字符串的指针),而
printf()
函数将采用一个参数(指向要显示的字符串的指针)。格式字符串),然后是堆栈中任意数量的参数(
printf()
是一个可变参数函数)。

请注意,绝对不会检查参数的数量,它仅取决于在格式字符串中遇到字符

%
的次数。例如,这种特殊性用于格式字符串格式错误利用方法,以交互方式探索进程堆栈的内容。

所以,基本上,区别在于

puts()
只有一个参数,而
printf()
是一个可变参数函数。

如果你想更好地理解这个区别,请尝试编译:

#include <stdio.h>

int main(void) {
    printf("testing %d", 10);
}

1
投票

(我发布此内容是为了回答帖子中提出的有关 arg-passing code-gen 的实际问题。关于

printf
puts
之间的差异,人们可能仅根据标题就想知道,而不是更一般的问题,如果这在他们的搜索中出现。)


没有任何原因,这只是错过了优化,并且您在禁用优化的情况下编译(默认为

-O0
编译快但不好),所以GCC甚至没有尝试。 只是 GCC 内部的一个怪癖。

此外,我无法使用 GCC 4.4.7 或我尝试过的任何其他版本重现

mov
-to-EAX https://godbolt.org/z/d511GWs59,无论有或没有
-maccumulate-outgoing-args
来制作较新的 GCC 仍然为参数保留空间并使用
mov
存储它们,而不是使用
push
。 (这是默认设置,但在 Pentium M 和更新版本变得普遍后发生了变化,因为它们有一个“堆栈引擎”,可以使推送/弹出单微操作。)
因此,这个错过的优化显然已在 Red Hat 4.4.5 和主线 4.4.7 之间修复了

int printf(const char*, ...)
是可变参数,因此它在 GCC 程序逻辑的内部表示(GIMPLE 然后 RTL)中的表示方式可能与调用具有固定数量参数的函数不同?

此外,如果要打印的文本是编译时常量并以换行符结尾(

printf
隐式附加,与
puts
不同,GCC 会尝试将
puts
优化为
fputs
。)GCC 甚至会这样做
-O0
,与其他编译器不同,除非您使用
-fno-builtin-printf
。 因此这可能会影响 GCC 内部的表示方式。

© www.soinside.com 2019 - 2024. All rights reserved.