哪个C 标准包括要添加到对象文件中的文件强制代码 /数据

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

本文关键字:文件 代码 数据 对象 标准 包括 添加 哪个      更新时间:2023-10-16

C 标准,因为C 11保证std::cout可以在具有有序初始化的静态对象的构造函数和损坏器中使用(只要在定义对象之前包含在内("(引用cppreference.com(。我不介绍此处的详细信息(例如,std::ios_base::Init等的角色(。例如,请参见在称为main((之前使用标准库函数安全吗?

但是,此保证意味着每当包含<iostream>时,编译器都必须确保将某些初始化代码添加到对象文件中(除非编译器/链接器存在一些优化,以避免这种情况(。我尝试了Godbolt编译器资源管理器:对于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编译器Explorer(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,功能,功能,实用程序,ctime,ctime,chrono,chrono,cstddef,cstddef,cstddef,pritializer_list,pritializer_list,pritializer_list,pritializer_list,pritializer_list,pritializer_list,pritializer_list,pritialiser_list,pritialiser_list,intimer,,unordered_set,unordered_map,stack,queue,迭代器,算法,cmath,cmath,complex,valarray,随机,数字,比例,cfenv,iosfwd,ios,ios,iStream,ostream,ostream,ostream,iostream,iostream,iostream,iostream,iostream,fstream,fstream,stream,stream,stream,stream,stream,iomanip,iomanip,iomanip,iomanip,cortd,cortd,cortd,cortd,corted,locale,clocale,codecvt,Regex,Atomic,thread,sutex,shared_mutex,future,procenty_variable,filesystem

有些似乎不支持(某些C 17,一些C 20(,所以我无法尝试:比较,版本,memory_resource,Contract,contract,span,范围,范围,执行,bit,syncstream,syncstream

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

在过去,格里·施瓦茨(Gerry Schwarz(发明了" Nifty Counter",以确保在任何可能使用之前的初始化标准流对象的初始化。确实,确实将开销添加到了执行#include <iostream>的每个翻译单元。

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

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

在各种启动功能中提供优先级是该表机制的直接扩展,并且初始化标准流比初始化用户定义的对象具有更高的优先级,因此启动机制可确保该机制确保流在使用它们的任何代码之前先初始化流。这是在标准库实施中完成的,因此代码仅在一个地方;如果链接器在流库中拉动,则还将获取流初始化代码。在任何翻译单元中无需任何额外的信息。

例如,

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

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