头文件iostream的包含导致二进制文件不同。

37

编译以下代码

int main() {
    return 0;
}
给予组装。
main:
        xorl    %eax, %eax
        ret

如果现在包含了iostream

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

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

创建了这个组件。

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

开启了完全优化 (-O3)。 https://gcc.godbolt.org/z/EtrEX8

有人可以解释一下,为什么包含一个未使用的头文件会改变二进制文件。 _GLOBAL__sub_I_main: 是什么?


1
C++的设计理念是不用的不需要付出代价(摘自《C++基础》第4页),这是一个崇高的目标,但在某些情况下无法实现。有很多东西可能会使C++二进制文件膨胀而又没有用处,你可能已经遇到了其中之一。请注意,语言本身并不要求它们存在;这是工具优化失败的结果。 - Matthieu M.
3
公平地说,这可以被称为一个“编程错误”。有点像无需虚拟化地使一个函数变成虚拟的。目标可以更好地表达为“你不支付你不请求的内容”。程序员需要知道他们在请求什么。 - StoryTeller - Unslander Monica
2
@StoryTeller:我不同意。如果优化器删除了对cout的任何调用实例,因为它证明了这些分支从未被执行; 你仍然会留下那些无用的残留物。如果你使用了printf,那么它将被完全消除。 - Matthieu M.
2个回答

33

每个包含<iostream>的翻译单元都包含一个ios_base::Init对象的副本:

static ios_base::Init __ioinit;

这个对象用于初始化标准流(std::cout以及其它相关的流)。这种方法被称为 Schwarz Counter,它确保标准流在第一次使用之前始终得到初始化(假设已经包含了iostream头文件)。

_GLOBAL__sub_I_main 函数是编译器为每个翻译单元生成的代码,用于调用该翻译单元中全局对象的构造函数,并安排相应的析构函数在退出时被调用。此代码由C ++标准库启动代码在调用main之前调用。


3
在正常终止之前,初始化并(或许更重要的是)清空它们。 - Jerry Coffin
有趣的是,__ioinit本身没有分配存储空间。同时,我想知道为什么构造函数没有被标记为comdat。这段代码真的在使用iostream的每个翻译单元中都重复了吗?看起来非常低效。 - fuz
1
@fuz _ZStL8__ioinit__ioinit 的存储空间。 - Maxim Egorushkin
1
@fuz - C++17的内联变量定义可以。 - StoryTeller - Unslander Monica
5
在不需要引用cin、cout、cerr、wcin、wcout和wcerr的翻译单元中,您应该包括<istream>和/或<ostream>,而不是<iostream> - zwol
显示剩余3条评论

23

包含头文件iostream会添加一个静态对象std::ios_base::Init的定义。该静态对象的构造函数将初始化标准流对象std::coutstd::cerr等等。

这样做的原因是为了避免静态初始化次序问题。它确保流对象在不同的翻译单元中被正确初始化。


1
换句话说,链接时优化可能能够省略这个,对吗? - Lightness Races in Orbit
@LightnessRacesinOrbit - 理论上,希望如此。 - StoryTeller - Unslander Monica
4
即使进行链接时优化,我认为编译器也没有足够的信息来理解在一个翻译单元中没有与任何标准流相关联的引用时,__ioinit是多余的。 - zwol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接