37

Compiling the following code

int main() {
    return 0;
}

gives the assembly

main:
        xorl    %eax, %eax
        ret

https://gcc.godbolt.org/z/oQvRDd

If now iostream is included

#include <iostream>   
int main() {
    return 0;
}

this assembly is created.

main:
        xorl    %eax, %eax
        ret
_GLOBAL__sub_I_main:
        subq    $8, %rsp
        movl    $_ZStL8__ioinit, %edi
        call    std::ios_base::Init::Init() [complete object constructor]
        movl    $__dso_handle, %edx
        movl    $_ZStL8__ioinit, %esi
        movl    $_ZNSt8ios_base4InitD1Ev, %edi
        addq    $8, %rsp
        jmp     __cxa_atexit

Full optimization is turned on (-O3). https://gcc.godbolt.org/z/EtrEX8

Can someone explain, why including an unused header changes the binary. What is _GLOBAL__sub_I_main:?

schorsch312
  • 5,553
  • 5
  • 28
  • 57
  • 1
    The C++ design philosophy of *What you don't use, you don't pay for* ([Foundations of C++, p. 4](http://www.stroustrup.com/ETAPS-corrected-draft.pdf)) is a lofty goal, however there are some situations in which it is not attained. There are quite a few things that can bloat a C++ binary and yet be useless, and you've stumbled upon one of them. Do note that the language itself does not mandate them; those are failures of tools to optimize them away. – Matthieu M. Aug 30 '18 at 09:26
  • 3
    @MatthieuM. - To be fair, this can be called a [programming error](https://stackoverflow.com/questions/52079248/include-of-iostream-leads-to-different-binary/52079357#comment91126611_52079357). Kinda like making a a function needlessly virtual. The goal can be better expressed as "you don't pay for what you don't ask". It's the programmer that needs to know what they are asking for. – StoryTeller - Unslander Monica Aug 30 '18 at 10:09
  • 2
    @StoryTeller: I disagree. If the optimizer removes any instance of calls to `cout` because it proved the branches were never taken; you still will end-up with those useless leftovers. If you had used `printf` instead, then it would be fully eliminated. – Matthieu M. Aug 30 '18 at 10:51

2 Answers2

33

Each translation unit that includes <iostream> contains a copy of ios_base::Init object:

static ios_base::Init __ioinit;

This object is used to initialize the standard streams (std::cout and its friends). This method is called Schwarz Counter and it ensures that the standard streams are always initialized before their first use (provided iostream header has been included).

That function _GLOBAL__sub_I_main is code the compiler generates for each translation unit that calls the constructors of global objects in that translation unit and also arranges for the corresponding destructor calls to be invoked at exit. This code is invoked by the C++ standard library start-up code before main is called.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • 3
    Initialize, and (perhaps more importantly) flush them before normal termination. – Jerry Coffin Aug 29 '18 at 14:05
  • Interestingly, no storage for `__ioinit` itself is allocated. At the same time I wonder why the constructor is not in marked comdat. Is this code really duplicated in every single translation unit that uses `iostream`? Seems really inefficient. – fuz Aug 29 '18 at 14:13
  • 1
    @fuz `_ZStL8__ioinit` is the storage for `__ioinit`. – Maxim Egorushkin Aug 29 '18 at 14:14
  • @MaximEgorushkin Ah, I see. Even less efficient. One useless byte wasted just because there is no better way to do a singleton initialisation in C++. – fuz Aug 29 '18 at 14:15
  • 1
    @fuz - C++17's inline variable definitions can. – StoryTeller - Unslander Monica Aug 29 '18 at 14:30
  • @StoryTeller is defining the stream (cout for example) as inline variable inside the iostream header making the whole schwartz/niffty counter obsolete? and is it even superior? because we can still make the schwartz counter fail – phön Aug 29 '18 at 20:51
  • 5
    This is why you should include `` and/or `` instead of `` in translation units that don't need to refer to cin, cout, cerr, wcin, wcout, and wcerr. – zwol Aug 30 '18 at 01:51
  • @phön - I don't know. But it sounds like a great topic for a language lawyer question. Personally, I don't think it's a silver bullet, but inline variables do seem promising. – StoryTeller - Unslander Monica Aug 30 '18 at 06:19
23

Including the iostream header has the effect of adding the definition of a static std::ios_base::Init object. The constructor of this static object initializes the standard stream objects std::cout, std::cerr and so forth.

The reason it's done is to avoid the static initialization order fiasco. It ensures the stream objects are properly initialized across translation units.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458