4

I am working on a simple program for learning purposes and I see the following behavior:

If I attempt to read an environment variable using getenv it works as expected:

#include <stdio.h>
#include <stdlib.h> 

int main() {
    printf("PATH is %s", getenv("PATH"));
    return 0;
}

If I do not include stdlib.h, I get the expected warning:

env.c:7:26: warning: implicit declaration of function ‘getenv’; did you mean ‘getline’? [-Wimplicit-function-declaration]

The program still compiles. Running the program with the header runs as expected, the program without the header included gets a seg fault.

If I inspect the disassembly of both programs, I see that there is a call to getenv@PLT

call    getenv@PLT

When I output the disassembly of the 2 programs (where the only difference in the c source code is the header include), the only difference I see is that when we include the header there's an instruction to set %eax to 0 before calling getenv.

movl    $0, %eax

If I step through in gdb for the program without included header, it does jump into getenv and does run a whole lot of code within the c runtime.

I am wondering what is the exact reason why not including the header here causes this kind of behavior? Why does c let this compile?

Some info: gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1) I am compiling like this: gcc -O0 -S env.c

papplesharp
  • 272
  • 1
  • 9
  • Try `gcc -Wall -Wextra -pedantic`. – melpomene Jul 15 '19 at 04:43
  • Related: https://stackoverflow.com/questions/5469274/what-does-plt-mean-here – ivan_pozdeev Jul 15 '19 at 04:44
  • It assumes the function returns an `int` and misinterprets the result. Don’t do it. – Jonathan Leffler Jul 15 '19 at 04:54
  • 1
    Why not post the generated assembler code here? – melpomene Jul 15 '19 at 04:56
  • 1
    If you're compiling for 64 bits, it is because `int` and pointers have different sizes and an undeclared `getenv`, which returns a pointer, is considered returning a `int`. But even in that case, there should be other differences in assembly code than just setting eax to zero ; there should be a difference with the parmeter pushed on stack before `printf` call. – Joël Hecht Jul 15 '19 at 05:35
  • @JoëlHecht: Yeah I suspect there are more differences than that. But why `movl $0, %eax` at all *before* the call to getenv()? `%eax` holds the return value from the function call, so setting it before is nonsensical unless maybe the function returns `void`. – Nemo Jul 15 '19 at 14:41

1 Answers1

2

In C language, if you forget to include the header file, the getenv() function is interpreted as int getenv(...). If you include the header file, the getenv() function is interpreted as char* getenv(const char*). Because of implementation of printf() the only difference in your case is the size of the return value. If sizeof(int) == sizeof(char*), then you have no problem (this happens for 32-bit programs). If sizeof(int) != sizeof(char*), you get an error (this happens for 64-bit programs). This is explained in the comment #5

Sergey
  • 522
  • 2
  • 8