【问题标题】:Why does the MSVC C++ compiler expand a simple Hello World into 4000 lines of assembly?为什么 MSVC C++ 编译器将一个简单的 Hello World 扩展为 4000 行汇编?
【发布时间】:2020-05-18 21:22:54
【问题描述】:

最近我正在潜心优化我的 C++ 代码,因此开始使用编译器资源管理器。由于我主要使用 Visual Studio 在 Windows 上进行开发,因此我使用了 msvc 编译器。

在某些时候,msvc 失控了。经过一番摆弄之后,我可以将其缩小到 iostream 标头,这应该是 I/O 的首选(SL.io.3)。

#include <iostream>
int main() {
    std::cout << "Hello World!\n";
    return 0;
}

虽然 gcc 或 clang 的总输出(main + 一个调用一些ios_base init 函数的静态初始化程序)总共大约 20 行汇编(在 Godbolt 编译器资源管理器过滤掉指令和 cmets 之后)。
MSVC explodes it 进入4000. 这些行中的大多数是单独的函数; MSVC 对main 的定义本身是 7 条指令,而 gcc/clang 是 8 条指令。 (gcc/clang 使用 GNU/Linux libstdc++ 将一个额外长度的 arg 传递给 cout 运算符重载函数,而不仅仅是 MSVC 在使用其自己的 C++ 库时所做的 2 个指针。)

如果我改用 puts 之类的东西,MSVC 的总输出相当紧凑,可以与 gcc/clang 相媲美,例如 here。

有人可以向我解释这里发生了什么,我做错了什么或指出我正确的方向吗?

为什么对于使用 C++ 库的简单函数而言,MSVC asm 列表如此臃肿?

【问题讨论】:

  • 你没有做错什么
  • 您会发现,在所有编译器的情况下,到链接完成时,该代码无论如何都会在您的可执行文件中结束
  • 似乎 MSVC 只包含所有标准库模板实例化 - 例如你可以在那里找到 basic_string(const char*) ctor 等等。生成的程序集列表可能是自包含的,可以由外部汇编器编译。实际的main 函数在main PROC 和main ENDP 行之间。
  • 请注意,编译器资源管理器会减少程序集输出以尝试仅显示相关的内容。考虑到它最初不是为 MSVC 构建的,它有可能摆脱了 GCC/Clang 的东西,而 MSVC 也缺少了同样的东西。另请注意,默认情况下,每个编译器可能包含或多或少的调试内容。例如,我的 Visual Studio 为我提供了 49 KB 的可执行文件用于调试版本,而 11 KB 的可执行文件用于发布版本。
  • @dewaffled:是的,MSVC 发出库模板的定义包括它不调用的模板。但是不,那不是为了让它自成一体。事实上恰恰相反; g++ 确实通过将其 asm 输出提供给单独的汇编器(as 或 gas)来工作。被调用但未定义的函数在libstdc++ 中。 MSVC 的 asm 输出模式包含额外的东西,如果你想将它实际组装成一个目标文件,你必须删除这些东西,你可以链接到一个工作的可执行文件。 AFAIK,如果您实际编译,大部分额外的 asm 都不会作为函数定义出现在 .obj 中。

标签: c++ visual-studio assembly visual-c++ compiler-explorer


【解决方案1】:

这可能不是一个完整的答案,但我想我可以解释很多差异。

许多标准库(例如 iostreams)都是模板繁重的代码。我相信 Microsoft 编译器会生成更多模板实例化,并依赖链接器来删除不必要的实例化。我认为这是 Windows 链接器使用的策略与大多数 Posix 策略不同的结果,但这也可能是简单地使用不同的标准库实现的结果。

如果您指定/MD,它告诉编译器您打算使用标准库的 DLL 版本,生成的代码将从 4000+ 行减少到少于 500 行。我不知道为什么会这样。也许 MSVC 知道 DLL 库具有所有必要的模板实例化,而静态库依赖于编译器的模板实例化。

您可以通过仅处理 C++ 异常(使用/EHs)来获得增量改进。默认情况下,编译器也会生成处理异步系统异常的代码。虽然您的 hello-world 示例没有明确使用异常,但标准库的某些部分可能会这样做。在这一点上,看起来很多额外的行都在设置堆栈展开表并调用析构函数。

MSVC 版本中的许多剩余多余部分看起来像是在调用析构函数时展开堆栈,因此异常处理模型可能不同。

我认为编译器资源管理器过去有一个“clang-cl”选项,但我现在看不到它。一般来说,clang-cl 是一个命令驱动程序,它解释 cl.exe 选项并调整默认选项以使 clang 生成与 Microsoft 代码兼容的二进制 ABI 代码。看看它是否会像常规 clang 一样生成代码,或者最终会发出更像 MSVC 的代码,这会很有趣。

【讨论】:

  • 您确定所有出现在 asm 列表输出中的函数定义实际上都会出现在 .obj 目标文件中吗? Godbolt 不允许 MSVC 的“编译为二进制”选项,而且它不会在 .obj 文件处停止。我读过 MSVC 的 asm 列表输出是信息性的,并不是它在编译时放入目标文件的内容。所以 IDK 有多少解释是纯粹在制作 asm 列表时过于冗长,而链接器将删除多少额外功能。
  • @PeterCordes:是的,所有这些函数定义和 vtable 都存在于 .obj 文件中。我在本地构建它,然后在带有/DISASM 和/SYMBOLS 的.obj 上使用dumpbin。每个函数在其自己的 COMDAT 中。应该来自标准库的符号通常标记为“pick any”。如果选择了相关的 comdat,则生成的调试信息的 Comdat 将被标记为选择。生成的 vtable 的 Comdats 看起来都是“选择最大的”。许多生成的符号出现与异常处理/展开/RTTI有关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-20
  • 1970-01-01
  • 2012-09-10
  • 2013-03-10
  • 2011-01-11
相关资源
最近更新 更多