11

This is my C program using puts():

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

After using gcc -S -o sample.s sample.cto compiled it into Assembly, this is what I got:

        .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

I did the same way, this time I was using printf() instead of puts and this is what I got:

    .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

Here is what I don't understand, the printf() function mov $.LC0 to %eax, then mov %eax to (%esp) while the puts() function mov %.LC0 directly to (%esp). I don't know why is that.

perror
  • 7,071
  • 16
  • 58
  • 85
tintheanh
  • 146
  • 1
  • 2
  • 5
  • 6
    It's probably because `printf` is a varargs function, and they get called differently from functions that have a fixed number of arguments. – Barmar Apr 30 '16 at 22:10
  • 1
    Interestingly enough, using clang on os x, both programs compile into the same assembly. – Leandros Apr 30 '16 at 22:13
  • Also why is GCC compiling position dependent code on linux? – Leandros Apr 30 '16 at 22:15
  • 6
    Turn on optimization when compiling. Without optimization GCC generates bad code that does lots of unnecessary things. – Ross Ridge Apr 30 '16 at 22:28
  • @RossRidge: that's kind of an inverse description of what 'optimization' is designed to do. – Jongware Apr 30 '16 at 22:30
  • does `-O3` make difference? – Serge Apr 30 '16 at 23:26
  • 2
    Assembly output doesn't matter at less than max optimization level , you're seeing the compiler making the code more verbose than needs be so that it's easier to follow in a debugger or human reading – M.M May 01 '16 at 00:10
  • gcc knows it can optimize `printf("foo\n")` to `puts("foo")`. Maybe a transformation did something to the internal representation of the function call, but then it noticed that there was no newline on the end of the printf arg. Or the varags vs. not might be the difference, even though in this ABI they get called the same way. (@Barmar: were you thinking of 64bit, where varags functions need `al` set to the number of FP args in FP regs?) Anyway, you're just seeing an artefact of compiler internals because you told the compiler to compile quickly instead of make good code. – Peter Cordes May 02 '16 at 06:10
  • @PeterCordes I was making an assumption, I didn't actually check the ABI. – Barmar May 02 '16 at 15:53
  • @Leandros why shouldn't it? Position dependent code is the default if `-fpic`/`-fPIC` is not used. – davmac Aug 20 '16 at 12:51

1 Answers1

7

The big difference between the two functions, at the assembly level, is that the puts() function will just take one argument (a pointer to the string to display) and the printf() function will take one argument (a pointer to the format string) and, then, an arbitrary number of arguments in the stack (printf() is a variadic function).

Note that, there is absolutely no check of the number of arguments, it is only depending of the number of time the character % is encountered in the format string. For example, this specificity is used in format string format bug exploitation method to interactively explore the content of the stack of a process.

So, basically, the difference is that puts() has only one argument and printf() is a variadic function.

If you want to better understand this difference, try to compile:

#include <stdio.h>

int main(void) {
    printf("testing %d", 10);
}
perror
  • 7,071
  • 16
  • 58
  • 85
  • 1
    Also worth mentioning that `puts` appends a `\n` implicitly, unlike `fputs`. See also [two cases where the compiler optimizes printf to puts, and two cases where it doesn't](https://godbolt.org/g/w2d2fI). And http://stackoverflow.com/questions/36343733/o2-optimizes-printfs-n-str-to-putsstr. – Peter Cordes May 27 '16 at 17:53