25

In the following C code:

#include <stdio.h>
int main(void){getchar();}

It produces the following asm:

main:
        push    rbp
        mov     rbp, rsp
                 # no extra instruction here when header is included
        call    getchar
        mov     eax, 0
        pop     rbp
        ret

However, if I don't include stdio.h in the file, then it still compiles, but adds in what looks like a random mov eax, 0 instruction:

enter image description here

Here is Compiler Explorer: https://godbolt.org/z/3fTcss. Is this just part of "undefined behavior", or is there a particular reason that the instruction before call getchar is added there?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
carl.hiass
  • 1,526
  • 1
  • 6
  • 26
  • 14
    Without a prototype the compiler generates code to handle a varargs function to be safe and that means zeroing `al`. – Jester Mar 10 '21 at 20:31
  • 1
    @Jester: I take it this means I can't pass 256 numbers to snprintf at once. – Joshua Mar 11 '21 at 00:59
  • 2
    You can. `al` only means the number of vector registers used which is maximum 8 (xmm0-xmm7). The rest of the arguments go on the stack. – Jester Mar 11 '21 at 01:28
  • I wonder why the compiler isn't using `xor` to clear `eax`. – Simon Richter Mar 11 '21 at 09:35
  • 1
    @SimonRichter It does, at `-O2` and above. `mov`ing a zero in there is just the unoptimized, obvious, close-to-C version. – TooTea Mar 11 '21 at 11:00

1 Answers1

37

Without the header, gcc provides an implicit declaration of getchar when you use it, as if you had previously declared

int getchar();

(This behavior was guaranteed by older versions of the C standard. Current versions make it undefined behavior to use a function which was not previously declared, but gcc still provides the old behavior as an extension.)

This declaration provides no information about the types of arguments which getchar expects. (Remember that unlike in C++, a declaration with () doesn't declare a function as taking no arguments, but as taking unspecified arguments, leaving it up to the programmer to know what the function expects and pass the proper number and type of arguments.) For all the compiler knows, it could even be variadic, and per the x86-64 SysV ABI, variadic functions expect in al the number of vector registers which are being used to pass in arguments. Here no vector registers are being used, so the compiler sets al to 0 before the call. (It's slightly more efficient to actually zero all of rax so it does that instead.)

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • 7
    The C standard from the very beginning said that implicitly declared functions and those declared without a prototype could not be variadic. But most compilers support it anyway. – prl Mar 10 '21 at 23:22
  • @prl: Wait what? The oldest C didn't have argument types in the declaration. – Joshua Mar 11 '21 at 00:42
  • 5
    @Joshua: The first C standard (C89) did. You're right that earlier pre-standard C did not. – Nate Eldredge Mar 11 '21 at 00:45
  • 3
    Current versions don't make use of undeclared functions UB. The implementation must emit a diagnostic, and may either stop compilation, or implicitly define the function and continue as it would in C89. I didn't look in the Standard itself, but at least the C99 Rationale document is quoted [here](https://stackoverflow.com/a/17937964/673852). – Ruslan Mar 11 '21 at 20:12