繁体   English   中英

哪个 C++ 标准包含文件强制将代码/数据添加到目标文件?

[英]Which C++ standard include files force code / data to be added to object files?

自 C++11 以来的 C++ 标准保证std::cout “可用于具有有序初始化的静态对象的构造函数和析构函数(只要在定义对象之前包含)”(引自cppreference.com ) . 我不在这里详细介绍(例如std::ios_base::Init的作用等)。 参见示例在调用 main() 之前使用标准库函数是否安全?

然而,这种保证意味着每当包含<iostream> ,编译器必须确保将一些初始化代码添加到目标文件中(除非编译器/链接器存在一些可以避免这种情况的优化)。 我用 Godbolt Compiler Explorer 试过这个:对于 ARM gcc 5.4(linux) 和 -O2,下面的代码

int main() {
}

编译为

main:
        mov     r0, #0
        bx      lr

而代码

#include <iostream>
int main() {
}

编译为

main:
        mov     r0, #0
        bx      lr
_GLOBAL__sub_I_main:
        stmfd   sp!, {r4, lr}
        ldr     r4, .L4
        mov     r0, r4
        bl      std::ios_base::Init::Init() [complete object constructor]
        mov     r0, r4
        ldr     r2, .L4+4
        ldr     r1, .L4+8
        bl      __aeabi_atexit
        ldmfd   sp!, {r4, lr}
        bx      lr
.L4:
        .word   .LANCHOR0
        .word   __dso_handle
        .word   _ZNSt8ios_base4InitD1Ev
.LANCHOR0 = . + 0

因此,仅仅包含<iostream>增加代码大小和初始化时间。 对于单个文件,影响可能被认为可以忽略不计。 但是,将此类包含指令不必要地添加到广泛使用的库头文件中,IMO 仍将视为可避免的资源浪费。 我将其视为保持包含指令清洁的另一个(即使不强)论据。

也就是说,我的问题是,是否有其他由标准(最好是最新版本)定义的头文件也仅通过包含(即,没有对头文件内容的任何实际引用)导致一些代码/数据添加到生成的目标文件中? 请注意,我不将此问题限制在初始化场景中 - 可能还有其他原因。

一些补充说明:

  • 可能会对符号表大小产生影响。 这对我来说不感兴趣——我感兴趣的是代码大小、数据大小和性能。
  • 我知道,即使从未调用内联函数,非优化编译器也可能为内联函数生成代码(外联)。 您可以假设启用了防止这种情况发生的优化。

这实际上并不在标准的范围内,但是 IOStream 的合理实现确实要求此初始化代码(否则std::cout将不可用,尽管有一些其他的静态状态可以共享)。

我个人没有在库的其他部分遇到过这种情况,我想不出容器或算法这样做的原因。 我可以想象一些线程子系统可能涉及一些预先初始化。

最终,对于您的工具链和平台,您知道的唯一方法就是尝试它。 一个快速的脚本会依次生成包含各种标准头文件的 C++ 源文件,依次将它们传递给您的编译器并检查生成的程序集,将按简短的顺序显示答案。

正如@eerorika 正确评论的那样,我可以将我用于<iostream>的机制与所有其他标准标头一起使用。 因此,我尝试了以下代码段,其中包含来自https://en.cppreference.com/w/cpp/header 的每个标头(实验标头和 C 兼容性标头除外)。 再次,我使用 Godbolt 编译器资源管理器 ( https://godbolt.org/ ),这次使用 ARM gcc 8.2 和选项-O2 -std=c++17

#include<xxx>  <--- xxx was exchanged for each of the headers below
int main() {
}

这是头文件列表:cstdlib、csignal、csetjmp、cstdarg、typeinfo、typeindex、type_traits、bitset、function、utility、ctime、chrono、cstddef、initializer_list、tuple、any、optional、variant、new、memory、scoped_allocator、climits、 cfloat、cstdint、cinttypes、限制、异常、stdexcept、cassert、system_error、cerrno、cctype、cwctype、cstring、cwchar、cuchar、string、string_view、charconv、数组、向量、双端队列、列表、forward_list、set、map、unordered_set、 unordered_map、堆栈、队列、迭代器、算法、cmath、复杂、valarray、随机、数值、比率、cfenv、iosfwd、ios、istream、ostream、iostream、fstream、sstream、strstream、iomanip、streambuf、ostream、cstdio、locale、 clocale,codecvt,正则表达式,原子,线程,互斥,shared_mutex,未来,condition_variable,文件系统

有些似乎不受支持(有些是 C++17,有些是 C++20),所以我无法尝试它们:比较、版本、内存资源、合同、跨度、范围、执行、位、同步流

结果:仅对于<iostream>我在汇编代码中获得了上述额外内容。

回到过去,Gerry Schwarz 发明了“漂亮的计数器”以确保在任何可能的使用之前初始化标准流对象。 这确实增加了执行#include <iostream>每个翻译单元的开销。

但这在很大程度上是因为 cfront 的局限性:它将 C++ 代码编译为 C,然后依靠本机编译器将该 C 代码转换为可执行文件。

现在我们有 C++ 的本地编译器,它们可以做 cfront 不能轻易做到的事情。 特别是,它们通常具有某种或多或少复杂的内置机制,用于在进入main之前初始化事物。 这通常使用从启动代码调用的函数表; 对于必须在main之前初始化的全局和文件范围对象,每个翻译单元通过链接器添加到该表中。

这是对该表机制的直接扩展,以在各种启动函数之间提供优先级,并且初始化标准流比初始化用户定义对象获得更高的优先级,因此启动机制确保在任何使用它们的代码之前初始化流。 这是在标准库实现中完成的,所以代码只在一处; 如果链接器拉入流库,它也会获取流初始化代码。 不需要任何翻译单元中的任何额外信息。

例如,Borland 的编译器允许 256 个不同的优先级。 仅使用了其中的少数优先级,但该机制允许对启动顺序进行微调控制。

简而言之:包含头文件不需要生成额外的初始化代码; 这是一个实现细节,对于启动,有更好的方法来做到这一点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM