Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why don't C header files increase the binary's size?

I wrote the following C++ program

class MyClass {
public:
        int i;
        int j;
        MyClass() {};
};

int main(void)
{
        MyClass inst;
        inst.i = 1;
        inst.j = 2;
}

and I compiled.

# g++ program.cpp
# ls -l a.out
-rwxr-xr-x  1 root  wheel  4837 Aug  7 20:50 a.out

Then, I #included the header file iostream in the source file and I compiled again.

# g++ program.cpp
# ls -l a.out
-rwxr-xr-x  1 root  wheel  6505 Aug  7 20:54 a.out

The file size, as expected, was increased.

I also wrote the following C program

int main(void)
{
    int i = 1;
    int j = 2;
}

and I compiled

# gcc program.c
# ls -l a.out
-rwxr-xr-x  1 root  wheel  4570 Aug  7 21:01 a.out

Then, I #included the header file stdio.h and I compiled again

# gcc program.c
# ls -l a.out
-rwxr-xr-x  1 root  wheel  4570 Aug  7 21:04 a.out

Oddly enough, the executable files' size remained the same.

like image 867
PetrosB Avatar asked Aug 07 '09 18:08

PetrosB


1 Answers

By including iostream in your source file, the compiler needs to generate code to setup and tear down the C++ standard I/O library. You can see this by looking at the output from nm, which shows the symbols (generally functions) on your object file:

$ nm --demangle test_with_iostream
08049914 d _DYNAMIC
08049a00 d _GLOBAL_OFFSET_TABLE_
08048718 t global constructors keyed to main
0804883c R _IO_stdin_used
         w _Jv_RegisterClasses
080486d8 t __static_initialization_and_destruction_0(int, int)
08048748 W MyClass::MyClass()
         U std::string::size() const@@GLIBCXX_3.4
         U std::string::operator[](unsigned int) const@@GLIBCXX_3.4
         U std::ios_base::Init::Init()@@GLIBCXX_3.4
         U std::ios_base::Init::~Init()@@GLIBCXX_3.4
080485cc t std::__verify_grouping(char const*, unsigned int, std::string const&)
0804874e W unsigned int const& std::min<unsigned int>(unsigned int const&, unsigned int const&)
08049a3c b std::__ioinit
08049904 d __CTOR_END__
... (remaining output snipped) ...

(--demangle takes the C++ function names "mangled" by by the compiler and produces more meaningful names. The first column is the address, if the function is included in the executable. The second column is the type. "t" is code in the "text" segment. "U" are symbols linked in from other places; in this case, from the C++ shared library.)

Compare this with the functions generated from your source file without including iostream:

$ nm --demangle test_without_iostream
08049508 d _DYNAMIC
080495f4 d _GLOBAL_OFFSET_TABLE_
080484ec R _IO_stdin_used
         w _Jv_RegisterClasses
0804841c W MyClass::MyClass()
080494f8 d __CTOR_END__
... (remaining output snipped) ...

When your source file included iostream, the compiler generated several functions not present without iostream.

When your source file includes only stdio.h, the generated binary is similar to the test without iostream, since the C standard I/O library doesn't need any extra initialization above and beyond what's already happening in the C dynamic library. You can see this by looking at the nm output, which is identical.

In general, though, trying to intuit information about the amount of code generated by a particular source file based on the size of the executable is not going to be meaningful; there's too much that could change, and simple things like the location of the source file may change the binary if the compiler includes debugging information.

You may also find objdump useful for poking around at the contents of your executables.

like image 98
Commodore Jaeger Avatar answered Sep 22 '22 21:09

Commodore Jaeger