views:

615

answers:

7

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.

+7  A: 

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;
Ben M
`extern` definitions don't generate code; they just let the compiler know they exist, so the compiler can reference them if they're actually used.
bdonlan
Yes--a valuable clarification.
Ben M
I don't think they are `extern` -- and even if they are, there has to be definitions somehwere. Their ctors/dtors are one (maybe the only) cause for the executable size increase.
sbi
If they weren't `extern` (and they are, at least in VC++), they'd be instantiated in every source file that includes `iostream` ... not what you'd want.
Ben M
+9  A: 

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.

Michael
Yep, but this doesn't explain why `<iostream>` does increase the executable size.
sbi
+2  A: 

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>.

bdonlan
+3  A: 

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.

Artem Barger
A: 

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.

yves Baumes
+15  A: 

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.

Commodore Jaeger
A: 

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.

sbi