Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

Header files are typically just declarations and don't directly result in machine code being generated. The linker is smart enough not to pull in unused functions from the CRT, so just including stdio.h without using any of its functions would not result in more code in your executable.

EDIT: They can include inline functions, classes, and so on which do include code, but those should not result in an increase in your executable size until they are actually used.

iostream includes code. stdio.h does not.

More specifically, the following definitions in iostream (there are more than listed, and vary by compiler) reference objects created in the standard library, which are then linked into your code:

extern istream &cin;
extern ostream &cout;
extern ostream &cerr;
extern ostream &clog;

There are some static initializations in iostream, while in stdio.h there are only functions and they definitions. Therefore including iostream will produce executable of greater size.

Typically speaking, header files contain only information for the compiler, not actual code. For example:

struct foo {
  int x;
};

Structure definitions like that often appear in headers, but they don't actually cause code to be generated, as they only give the compiler information about how to handle 'foo', should it see it later. If it doesn't see foo, the information is lost when the compilation finishes.

In fact, having anything that does generate code will generally produce an error. For example:

void foo() {
  printf("bar!\n");
}

If this is in a header, and gets included in two .c files, the foo() function will be generated twice. This will cause an error at link. It's possible to avoid the error if you have a strong reason to do so, but generally actually generating code in headers is avoided if possible.

Note that one exception here is inline members in C++. For example:

class Foo {
  void bar() { /* ... */ }
};

Technically speaking bar() is generated in every file that includes this code. The compiler does various tricks to avoid the error (weak binding, etc). This may indeed increase the size of the executable, and is probably what you saw with <iostream>.

The <iostream> header comes with a couple of objects, 'std::cin, 'std::cout, std::cerr, and std::clog. These are instances of classes which have non-trivial constructors and destructors. These are code and have to be linked. This is what increases the executable's size.

AFAIK, <cstdio> doesn't come with code, so that's why there's no increase in executable size.

iostream file declared some global objects:

std::cout , std::cerr, std::cin which are ostream type.

Then the compiler will bring that class and compile it right into your final binary, adding so much to its size.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top