为什么C++的Hello World二进制文件比等价的C语言二进制文件要大?

4
在Bjarne Stroustrup的常见问题解答(FAQ)中,他说使用gcc -O2编译时,使用C和C++编写的Hello World文件的文件大小是相同的。
参考资料:http://www.stroustrup.com/bs_faq.html#Hello-world 我决定尝试一下,这是C语言版本:
#include <stdio.h>

int main(int argc, char* argv[])
{
    printf("Hello world!\n");
    return 0;
}

这里是C++版本

#include <iostream>

int main(int argc, char* argv[])
{
    std::cout << "Hello world!\n"; 
    return 0;
}

我在这里编译,但大小不同:

r00t@wutdo:~/hello$ ls
hello.c  hello.cpp
r00t@wutdo:~/hello$ gcc -O2 hello.c -o c.out
r00t@wutdo:~/hello$ g++ -O2 hello.cpp -o cpp.out
r00t@wutdo:~/hello$ ls -l
total 32
-rwxr-xr-x 1 r00t r00t 8559 Sep  1 18:00 c.out
-rwxr-xr-x 1 r00t r00t 8938 Sep  1 18:01 cpp.out
-rw-r--r-- 1 r00t r00t   95 Sep  1 17:59 hello.c
-rw-r--r-- 1 r00t r00t  117 Sep  1 17:59 hello.cpp
r00t@wutdo:~/hello$ size c.out cpp.out
   text    data     bss     dec     hex filename
   1191     560       8    1759     6df c.out
   1865     608     280    2753     ac1 cpp.out

我将 std::endl 替换为 \n,这样的修改使二进制文件更小了。本来认为这么简单的修改可以被内联,但结果让我失望了。

此外,优化编译后的汇编代码有数百行?我可以使用 sys_write 只用 5 条汇编指令编写出一个 hello world 程序,为什么会有这么多额外的东西呢?为什么 C 语言要在堆栈上存储这么多额外信息进行设置?相比之下,C 语言需要 8KB 的代码,而汇编只需要大约 50 字节,为什么呢?


9
不,他没有说那个。他说:“2004年,我在Unix上使用gcc -O2测试,iostreams和stdio两个版本的大小完全相同。” - Benjamin Lindley
4
你的问题具体是什么?还有,我认为声称 Bjarne 在这方面撒谎有点过了。在旧版本编译器和系统上,二进制文件可能具有相同的大小(即使在较新的系统上不是这样)。 - templatetypedef
3
你们这些缺乏想象力的喷子,根本不是“不清楚他在问什么”。这是一个重复的问题,与https://dev59.com/LmTWa4cB1Zd3GeqPHNaR相同! - Lightness Races in Orbit
1
@LightnessRacesinOrbit 不是这样的,文件大小相差两个数量级,并且它在Windows上。400 KB值得担心,500字节则是学术性的。 - Potatoswatter
5
虽然 OP 犯了一些错误,但这是一个合法的问题,有一个或多个合法的技术答案。程序之间的大小差异可以被测量和说明。 - R.. GitHub STOP HELPING ICE
显示剩余4条评论
2个回答

7

您正在查看一些容易被误解的信息。8559字节和8938字节的文件大小大部分都是头部带有符号名称和其他杂项信息,至少用于最小限度的调试目的,因此它们在很大程度上是无意义的。 稍微 有意义的数字是稍后您添加的 size(1) 输出:

r00t@wutdo:~/hello$ size c.out cpp.out
   text    data     bss     dec     hex filename
   1191     560       8    1759     6df c.out
   1865     608     280    2753     ac1 cpp.out

您可以使用-A选项来获取更详细的细分,但简而言之,在这里的差异相当微不足道。更有趣的是,Bjarne Stroustrup从未提到他是在谈论静态链接还是动态链接。在您的情况下,两个程序都是动态链接的,因此大小差异与stdio或iostream的实际大小成本无关;您只是测量调用代码的成本,或者(更可能是基于其他评论/答案)C++异常处理支持的基本开销。现在,常见的说法是,静态链接的基于iostream的C++ hello world程序甚至可以比基于printf的程序更小,因为编译器可以看到使用了哪些重载版本的operator<<并优化掉不必要的代码(例如昂贵的浮点数打印),而printf的格式字符串使用在常见情况下会很困难,并且在一般情况下是不可能的。但是,我从未见过一个静态链接的iostream-based hello程序能够比C中基于printf的程序更小,更不用说比其更小了。

5
我认为他把半个千字节当作了一个四舍五入误差。两者都是“9千字节”,这是在典型的文件浏览器中看到的。它们并不完全相同,因为在底层,C和C++库是相当不同的。如果您已经熟悉您的反汇编器,您可以自己查看区别的细节。
“额外的东西”是为了导入标准库shlib中的符号,并处理C++异常。奇怪的是,GCC编译的C可执行文件大部分都被C++异常处理表占用。我还没有想出如何使用GCC删除它们。 endl是内联的,但它包含调用打印\n字符和刷新流的函数,这些函数不是内联的。大小的差异是由于从标准库导入这些函数。
事实上,在任何具有动态加载库的系统上,单个千字节很少有影响。像嵌入式系统上的自包含代码需要包括其使用的标准库功能,而C++标准库往往比其C对应物更重 - 特别是<iostream><stdio.h>

1
@πάνταῥεῖ 当然,那是我尝试的第一件事:v)。如果您找到有效的解决方案,请告诉我。问题不在于生成异常,而在于为使用异常的函数调用提供支持。 - Potatoswatter
我需要检查一下我的同事的工作,他通过覆盖那个弱函数,巧妙地省略了标准的未捕获异常处理代码。我现在只是不记得确切的名称了。 - πάντα ῥεῖ
@πάνταῥεῖ 这些混乱代码根本不是可执行代码,而是告诉解包库如何解释堆栈的表格。 - Potatoswatter
如果这些表没有被任何代码引用,它们就不会被链接器调用,对吗? - πάντα ῥεῖ
@πάνταῥεῖ 这不是异常处理的工作方式。这些表格是由libunwind使用的。我建议您编译类似于“hello world”的东西,甚至是没有库调用的更简单的东西,然后检查编译后的大小并通过反汇编器运行它。 - Potatoswatter
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接