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


138

当使用相同的代码时,只需更改编译器(从C编译器到C ++编译器)将更改分配的内存量。我不太确定为什么会这样,并且想进一步了解它。到目前为止,我得到的最好的答复是“可能是I / O流”,它不是很具描述性,使我想知道C ++的“您不用为不使用的东西付钱”。

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

#include <stdio.h>

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

所使用的代码不会更改,但是无论我是以C还是C ++进行编译,它都会更改Valgrind基准测试的结果。但是,这些值在编译器之间保持一致。该程序的运行时分配(峰值)如下:

  • GCC(C):1,032字节(1 KB)
  • G ++(C ++):73,744字节(〜74 KB)
  • lang(C):1,032字节(1 KB)
  • 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,我运行valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-lang每种编译器和语言,然后ms_print显示峰。

我在这里做错什么了吗?


11
首先,您如何构建?您使用什么选项?您如何衡量?您如何经营Valgrind?
一些程序员花花公子

17
如果我没记错的话,现代的C ++编译器必须使用异常模型,在这种异常模型下,进入try块不会对性能造成任何影响,但会占用较大的内存,可能是带有跳转表之类的东西。也许尝试无例外地进行编译,看看有什么影响。编辑:实际上,反复尝试禁用各种c ++功能,以查看对内存占用量有何影响。
弗朗索瓦·安德列

3
当使用clang++ -xc而不是进行编译时clang,存在相同的分配,这强烈表明其归因于链接库
贾斯汀

14
@bigwillydos这确实是C ++,我看不到它破坏的C ++规范的任何部分...除了可能包括stdio.h而不是cstdio之外,但至少在较旧的C ++版本中允许这样做。您认为该程序的“格式错误”是什么?
价格

4
我发现这些gcc和clang编译器在C模式下生成的字节数完全相同,在模式下生成的字节数完全相同,这令人怀疑C++。你有抄写错误吗?
罗恩·约翰

Answers:


149

堆用法来自C ++标准库。它在启动时分配内存供内部库使用。如果您不反对它,则C和C ++版本之间的差应该为零。使用GCC和Clang,可以使用以下命令编译文件:

g ++ -Wl,-根据需要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”。)


50
在看到这个答案时,我的第一个想法是:“ 如果该标志有助于减少不必要的开销,为什么默认情况下不将其打开? ”。有一个好的答案吗?
纳特

4
@Nat我的猜测是在开发时编译速度较慢。准备好创建发行版时,您将其打开。同样,在普通/大型代码库中,差异可能很小(如果您使用了大量的STD库等)
DarcyThomas

24
@Nat该-Wl,--as-needed标志删除您在标志中指定-l但实际上未使用的库。因此,如果您不使用库,则不要链接它。您不需要此标志。但是,如果您的构建系统添加了太多的库,并且清理所有库并仅链接所需的库将需要大量的工作,那么您可以改用此标志。但是标准库是一个例外,因为它是自动链接的。所以这是一个极端的情况。
Nikos C.

36
@Nat-按需-可以具有不需要的副作用,它通过检查您是否使用了任何库符号并将那些未通过测试的符号踢出而起作用。但是:库也可以隐式执行各种操作,例如,如果库中有静态C ++实例,则将自动调用其构造函数。在极少数情况下,没有显式调用的库是必要的,但它们存在。
诺伯特·兰格

3
@NikosC。生成系统不自动知道您的应用程序使用哪些符号,以及哪些库实现它们(编译器,arch,发行版和c / c ++库之间的差异)。对于基本运行时库,正确地做到这一点相当麻烦。但是在极少数情况下,您需要一个库,您应该只使用--no-need-the-a-need-ne-reneed,并在其他地方按需保留-learned。我看到的一个用例是用于跟踪/调试(lttng)的库和执行某种身份验证/连接的库。
诺伯特·兰格

16

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

如果使用C或C ++编译器编译代码,则将获得相同的程序集。汇编器将产生相同的对象。区别在于,工具链驱动程序将为两种不同的语言向链接器提供不同的输入:不同的启动程序(C ++需要代码来执行在名称空间级别具有静态或线程本地存储持续时间的对象的构造函数和析构函数,并且需要用于堆栈的基础结构例如在异常处理期间支持展开的框架,C ++标准库(在命名空间级别还具有静态存储持续时间的对象)以及可能的其他运行时库(例如,具有堆栈展开基础结构的libgcc)。

简而言之,不是编译器引起了占用空间的增加,而是您通过选择C ++语言选择使用的东西的链接。

C ++确实具有“只为所用内容付费”的理念,但是通过使用这种语言,您可以为此付费。您可以禁用部分语言(RTTI,异常处理),但是随后您将不再使用C ++。如另一个答案中所述,如果您根本不使用标准库,则可以指示驱动程序将其忽略(--Wl,-按需),但是如果您不打算使用任何功能,关于C ++或其库,为什么还要选择C ++作为编程语言?


即使您不实际使用异常处理,也要付出代价,这是一个问题。通常,这对于C ++功能来说是不正常的,这是C ++标准工作组正在尝试的解决方法。请参阅Herb Sutter在ACCU 2019 De-fragmenting C ++上的主题演讲:使异常更加可负担和可用。但是,在当前的C ++中,这是一个不幸的事实。即使使用关键字添加新的新异常机制时,传统的C ++异常也总是会产生这种代价。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.