为什么 C + + 的初始分配要比 C 大得多?

当使用相同的代码时,简单地改变编译器(从 C 编译器改为 C + + 编译器)将改变分配的内存量。我不太确定这是为什么,也不想更多地理解它。到目前为止,我得到的最好的回答是“可能是 I/O 流”,这并不是很具有描述性,让我对 C + + 的“你不为你不使用的东西付费”这一方面感到好奇。

我使用的是 Clang 和 GCC 编译器,分别是7.0.1-8和8.3.0-6版本。我的系统运行在 Debian 10(Buster)上,最新版本。基准测试是通过 Valgrind Massif 完成的。

#include <stdio.h>


int main() {
printf("Hello, world!\n");
return 0;
}

所使用的代码不会改变,但是不管我是以 C 还是 C + + 的形式编译,它都会改变 Val哪个基准测试的结果。但是,这些值在不同编译器之间保持一致。程序的运行时分配(峰值)如下:

  • GCC (C) : 1,032字节(1KB)
  • G + + (C + +) : 73,744字节,(~ 74 KB)
  • Clang (C) : 1,032字节(1KB)
  • Clang + + (C + +) : 73,744字节(~ 74 KB)

对于编译,我使用以下命令:

clang -O3 -o c-clang ./main.c
gcc -O3 -o c-gcc ./main.c
clang++ -O3 -o cpp-clang ./main.cpp
g++ -O3 -o cpp-gcc ./main.cpp

对于瓦尔格林,我在每个编译器和语言上运行 valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-lang,然后运行 ms_print来显示峰值。

我做错什么了吗?

8663 次浏览

堆的使用来自 C++标准程式库。它在启动时为内部库分配内存。如果您没有链接到它,那么 C 和 C + + 版本之间应该没有任何区别。使用 GCC 和 Clang,您可以使用以下命令编译文件:

g++ -Wl,--as-needed main.cpp

这将指示链接器不要链接到未使用的库。在您的示例代码中, 没有使用 C + + 库, 因此它不应该与 C++标准程式库链接。

你也可以用 C 文件测试一下:

gcc main.c -lstdc++

堆的使用将重新出现,即使您已经构建了一个 C 程序。

堆的使用显然依赖于您正在使用的特定 C + + 库实现。在您的示例中,这是 GNU C + + 库 Libstdc + + 。其他实现可能不分配相同数量的内存,或者根本不分配任何内存(至少在启动时不分配)例如,LLVM C + + 库(Libc + + )在启动时不会进行堆分配,至少在我的 Linux 机器上是这样:

clang++ -stdlib=libc++ main.cpp

堆的使用与根本不对其进行链接是一样的。

(如果编译失败,那么可能没有安装 libc + + ,包名通常包含“ libc + +”或“ libcxx”。)

GCC 和 Clang 都不是编译器——它们实际上是工具链驱动程序。这意味着它们调用编译器、汇编程序和链接器。

如果您使用 C 或 C + + 编译器编译您的代码,您将得到生成的相同程序集。汇编程序将生成相同的对象。不同之处在于, 工具链驱动程序将为两种不同语言的链接器提供不同的输入: 不同的启动程序(c + + 需要在名称空间级别为具有静态或线程本地存储期限的对象执行构造函数和析构函数的代码, 并且需要堆栈帧的基础设施来支持在异常处理过程中的展开, 例如) , C++标准程式库(在名称空间级别也具有静态存储期限的对象) , 可能还有额外的运行时库(例如, 具有堆栈展开基础设施的 libgcc

简而言之,并不是编译器导致了内存占用的增加,而是通过选择 C + + 语言选择使用的内容的链接。

的确,C + + 有“只为你使用的东西付费”的哲学,但是通过使用这种语言,你就为它付费了。您可以禁用语言的某些部分(RTTI,异常处理) ,但这样您就不再使用 C + + 了。正如在另一个答案中提到的,如果你根本不使用标准库,你可以指示驱动程序省略它(—— Wl,—— As-need) ,但是如果你不打算使用 C + + 或其库的任何特性,为什么你甚至选择 C + + 作为一种编程语言?