返回

C++ 空程序为何占用 204KB 堆空间?原因与解决

Linux

C++ 空程序为何占用 204KB 堆空间?

当使用 C++ 编译器(例如 g++)编译一个空程序时,会发现其运行时会额外分配 204KB 的堆内存。而使用 C 编译器(例如 gcc)编译相同的空程序则不会出现这种情况。 这并非是简单的编译差异,其背后牵涉到 C++ 标准库的初始化机制。 本文将探讨这一现象背后的原因,并提供相应的解决方案。

问题根源:C++ 标准库的默认初始化

C++ 为了支持复杂的面向对象特性以及标准库功能,在程序启动时需要进行一些初始化操作,这些操作需要额外的内存空间。其中一个关键因素便是 C++ 标准库的全局对象初始化, 例如 iostream 和 locale 机制等。尽管在空程序中并没有显式使用这些功能,但是编译器依然会将其相关的资源在程序运行时初始化。这些初始化过程涉及到了动态内存分配,需要从堆上获取空间。

C 编译器则更为精简。 当不显式引用库,其在启动时不执行如此复杂的初始化过程,也就不需要申请额外的堆空间。

如何验证和观察?

为了清晰验证 C++ 的堆内存占用,可以使用 strace 工具观察系统调用,也可以使用 pmap 工具查看内存映射。

使用 strace 工具:

  1. C++ 编译: g++ main.cpp && strace ./a.out

  2. C 编译: gcc main.c && strace ./a.out

    对比输出会发现,使用 g++ 编译的程序会执行 brk 系统调用来增加堆的大小,而使用 gcc 编译的程序不会。

使用 pmap 工具:

  1. 编写 C++ 程序:

    #include <iostream>
    #include <unistd.h>
    
    int main()
    {
        char buf[1024];
        sprintf(buf, "pmap -XX %u", getpid());
        std::system(buf);
    
        return 0;
    }
    
  2. C++ 编译: g++ main.cpp -o cpp_app && ./cpp_app

    观察 [heap] 行的大小。

  3. C 编译: gcc main.c -o c_app && ./c_app
    ( 需要将 #include <iostream>std::system移除,否则无法编译,当然你也可以引入<stdlib.h>并使用 system 代替 )

C 程序的输出结果中可能根本没有 [heap] 行。

通过以上操作,可以验证 C++ 程序由于标准库初始化,确实在堆上额外申请了 204KB 的内存。

解决方案

理解了问题所在,可以通过以下方法解决 C++ 程序中不必要的堆空间占用:

  1. 移除 C++ 标准库的依赖

    当程序不需要使用任何 C++ 标准库(例如, iostream, string 等),可以将程序代码切换到 C 风格的代码编写,使用 gcc 进行编译。

    操作步骤:

    • 删除或注释掉#include <iostream> 以及任何涉及标准库的操作
    • 使用 gcc 编译源代码。 例如gcc main.c -o main_c

    这种方法本质上就是避开 C++ 库初始化过程,从而避免堆空间的占用。但是这样也放弃了C++标准库带来的便利性。

  2. 使用 -fno-exceptions 以及 -fno-rtti 选项:

    通过关闭 C++ 的异常处理以及运行时类型信息功能可以略微减少 C++ 启动时开销,但这并不会彻底移除对动态内存的需要。

    编译命令:

    ```bash
    

    g++ main.cpp -o main_noexcept -fno-exceptions -fno-rtti

    执行程序:
    
    ```bash
    ./main_noexcept
    

使用pmap 工具查看输出可以观察到 heap 内存可能仍然存在,只是大小略微减小。

  1. 针对嵌入式环境使用特定编译器标志:

有些 C++ 编译器,尤其是用于嵌入式系统的编译器,会提供选项,允许开发者控制是否进行全局对象的初始化, 这有助于减小最终的二进制体积以及启动时间,进而降低内存占用。 使用特定编译器的命令需要查阅编译器官方文档, 每一个嵌入式系统使用的编译器可能标志不同。 如下提供一些示例:

  • GNU 编译器可能支持 -fno-global-constructors

编译示例:

g++ main.cpp -o main_no_global_const -fno-global-constructors
 ```
执行程序:
```bash
./main_no_global_const

使用pmap 工具查看输出, 可能会减小甚至消除堆的占用,具体的表现取决于编译器实现。

安全建议

  • 按需使用: 仅仅在确实需要的时候才包含C++ 标准库头文件, 这样避免不必要的资源浪费。
  • 评估环境: 对于内存受限的系统,需要仔细权衡 C++ 的便利性带来的资源消耗。
  • 测试验证: 任何对编译配置或者链接选项的修改,都需要通过充分测试来验证程序的行为和资源消耗,避免潜在问题。
  • 文档学习: 使用特定编译器时,深入了解编译器所提供的特性,以及对应的编译选项,这有利于充分掌控编译的过程。

选择最合适的方案取决于实际的程序需求以及目标平台的限制。如果不需要使用 C++ 标准库,切换到 C 语言可能是最优选择, 如果需要保留 C++ 的便利性, 则需进行细致的编译参数配置与性能评估。

结论

C++ 程序的 204KB 额外堆空间并非异常,它源于标准库的默认初始化机制。理解了这一现象的根本原因,才能更好的选择合适的优化方案,从而确保应用程序运行的资源消耗符合预期。