【问题标题】:Are there any performance implications to including every header?包含每个标头是否会对性能产生影响?
【发布时间】:2015-11-13 17:08:56
【问题描述】:

假设我想使用hex() 函数。我知道它是在<ios> 标头中定义的,我也知道它包含在<iostream> 标头中。不同之处在于<iostream> 中有更多的功能和其他我不需要的东西。

从性能的角度来看,我是否应该关心包含/定义更少的函数、类等而不是更多?

【问题讨论】:

  • 它会影响二进制文件的大小,但我猜不会有任何性能问题。
  • @Haris:不,这不是真的,在一般情况下。
  • @PaulR,会有性能问题吗?
  • @Haris 它可能对二进制文件的大小影响很小(如果有的话)。编译时从头文件中获得原型和声明不会自动添加相关的可执行代码(除非使用它,然后在链接期间添加),并且通过动态链接,该代码将成为链接到库的一部分而不是可执行文件无论如何。
  • @Haris:不,我的意思是一般来说它不会影响生成的二进制文件的大小,正如你所声称的那样。

标签: c++ c header-files


【解决方案1】:

如果标准说它是在标头<ios> 中定义的,则包含标头<ios>,因为您不能保证它将包含在/通过任何其他标头中。

【讨论】:

  • 这通常是错误的,因为如果包含其他一些标头,您可以依赖一些符号来定义;考虑std::begin
  • 问题中是否有任何矛盾或需要的问题?
  • @black - 您可以依赖在标头中定义的符号或标准中定义它们的标头。很少需要出现在多个标题中。大多数实现最终会在多个标头中定义一些符号,因为它们是内部需要的,但这是特定于实现的,即不是您可以依赖的东西。
【解决方案2】:
  • 运行时性能没有受到影响。
  • 但是,如果包含大量不必要的标头,则可能会导致编译时间过长。
  • 此外,完成此操作后,您可以创建不必要的重新编译,例如,如果更改了标头但不使用它的文件包含它。

在小型项目(包括小标题)中,这无关紧要。随着项目的发展,它可能会。

【讨论】:

  • 在标头中静态初始化变量可能会增加一些运行时开销。
  • 你是对的。但我认为标题中的变量要么是错误完成的,要么是用于高级目的的罕见事件。
【解决方案3】:

TL;DR:一般来说,最好只包含您需要的内容。包含更多可能对二进制大小和启动产生不利影响(应该是微不足道的),但在没有预编译头文件的情况下主要损害编译时间。


当然,您必须至少将这些标题一起包含在内,以保证涵盖您的所有用途。
无论如何,它有时可能会发生“工作”,因为标准 C++ 标头都可以根据实现者的需要相互包含,并且标头可以在 std-namespace 中包含其他符号(请参阅Why is "using namespace std" considered bad practice?) .

接下来,有时包含额外的标头可能会导致创建额外的对象(请参阅std::ios_base::Init),尽管设计良好的库会最小化此类(据我所知,这是标准库中的唯一实例)。

但最大的问题实际上不是编译(和优化)二进制文件的大小和效率(除了前面的一点,它应该不受影响,其影响应该很小),而是积极开发时的编译时间(参见还有How does #include <bits/stdc++.h> work in C++?)。
后者(严重,以至于委员会正在研究模块提案,请参阅C++ Modules - why were they removed from C++0x? Will they be back later on?)受到添加多余标题的不利影响。

除非您自然地使用预编译头文件(请参阅Why use Precompiled Headers (C/C++)?),在这种情况下,在预编译头文件中包含更多内容,因此在任何地方而不是仅在需要的地方,只要不修改这些头文件,实际上会减少大部分时间都是编译时间。

有一个基于 clang 的工具用于找出最小标头,称为 include-what-you-use
它分析 clang AST 来决定,这既是优势也是劣势:
您不需要教它有关标题提供的所有符号,但它也不知道事情是否在该修订中以这种方式解决,或者它们是否是合同性的。
所以你需要仔细检查它的结果。

【讨论】:

    【解决方案4】:

    包含不必要的标题有以下缺点。

    1. 更长的编译时间,链接器必须删除所有未使用的符号。
    2. 如果您在 CPP 中添加了额外的标头,它只会影响您的代码。
    3. 但是,如果您将代码作为库分发,并且您在头文件中添加了不必要的头文件。客户端代码将负责定位您使用的标头。
    4. 不要相信间接包含,使用实际定义所需函数的标头。
    5. 此外,作为良好的编程实践,项目中应包含标题以减少依赖性。
    //local header -- most dependent on other headers
    #include <project/impl.hpp>
    //Third party library headers -- moderately dependent on other headers
    #include <boost/optional.hpp>
    //standard C++ header -- least dependent on other header
    #include <string>
    

    并且不会受到影响的是运行时,链接器将在编译期间删除未使用的符号。

    【讨论】:

    • 我不会打扰第 5 点。显然,应该首先包含相应的头文件,以确保没有依赖问题,但其余部分应该按字母顺序排序,因为这很容易并且可以可靠地搜索任何特定的,让编译器(和标头保护)对其进行排序。
    • 链接器不必“删除所有未使用的符号”...只要函数或对象未被实际访问或在它正在编译的代码中定义(不仅仅是声明)。
    • +1 "不要相信间接包含。" Stephan T. Lavavej,(“STL”)是 Visual Studio 的 STL 实现的维护者。他明确表示这是因为在某些情况下类型在标头中移动,如果您说,#include iostream 知道它还包括 std::cinistreamiostream 停止包括 istream 您的代码中断。始终包含所需类型的标头,不要依赖间接。
    • @Dmitri 允许链接器删除在目标代码中没有被其他任何地方引用的符号,只要它们没有被导出。
    • @Casey 你可以信任标准库.. 但你可能不想为第三方库这样做......就像你的记录器库有 boost::format 并且你选择忽略“boost/format .hpp”,但是一旦你的记录器改变了它的实现,你的代码就会崩溃。
    【解决方案5】:

    包含不需要的头文件有一些值。

    1. 包含通常需要的includes 的剪切和粘贴确实需要较少的编码工作。当然,后来的编码现在受到了不知道真正需要什么的困扰。

    2. 特别是在 C 中,其名称空间控制有限,包括不需要的标头,可以迅速检测到冲突。假设代码定义了一个恰好符合标准的全局非static 变量或函数,例如erfc() 进行一些文本处理。通过包含&lt;math.h&gt;,可以检测到与double erfc(double x) 的冲突,即使这个.c 文件没有FP 数学,而其他.c 文件有。

      #include <math.h>
      char *erfc(char *a, char *b);
      

    OTOH,如果不包含此.c 文件&lt;math.h&gt;,则在链接时将检测到冲突。如果代码库多年来不需要 FP 数学,而现在需要,那么这种延迟通知的影响可能会很大,只是检测到在许多地方使用的char *erfc(char *a, char *b)

    IMO:尽合理努力不包含不需要的头文件,但不要担心包含一些额外的头文件,尤其是常见的头文件。如果存在自动化方法,请使用它来控制头文件包含。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-02
      相关资源
      最近更新 更多