使用MinGW编译器的C++可执行文件大小

6

我通常参加64kb大小编码比赛,使用C语言编写,但考虑转向C++。

在MinGW g++下,我遇到了一些.exe文件大小的问题(在使用可执行文件压缩器之前,我必须将其缩小到小于100 kb)。

我看过这篇文章:如何减少MinGW g ++编译器生成的可执行文件大小? 但是我已经在使用MinGW / g ++ 4.8.1并使用了-s -Os选项...详见下文(对于4.8.1也是如此:无法识别的选项'-shared-libstdc ++'和'找不到-lstdc ++_s')。

这个简单的helloworld只有10 kb(这很好):

#include "windows.h"
int main() {
    MessageBoxA(0, "test", "test", 0);
    return 0;
}

但是当我添加以下内容时:
#include <string>
...
std::string asdf;

当我添加后,它变成了193 kb。

#include <iostream>

然后它变成了756千字节。

我正在使用以下标志:

-std=c++11
-Wall
-s       (or -Wl,-s)
-Os
-DNDEBUG
-fno-exceptions
-fno-rtti
(note: removed those with no effect)

有一种方法只链接我所使用的内容,我错过了什么吗?

可选项1:在MinGW/g++ 4.8.1上是否可能使-shared-libstdc++或-lstdc++_s起作用?

可选项2:当我尝试使用-nostdlib并将main替换为WinMain时:

    #include "windows.h"
    int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
        LPSTR lpCmdLine, int nCmdShow) {
        MessageBoxA(0, "test", "test", 0);
        return 0;
    }

我的编译器没有报错,但运行时却出现了崩溃。当编译为C语言时,一切都正常。(可选的,因为我不想让您/我花费时间调试crt-startup,一个可以裁剪库的编译器链接标志会更有帮助)


1
你是否可以使用 strip 命令? - billz
Unix世界的命令?不是。 - ben
2
我刚刚从Mingw Binutils下载了Windows版本的strip,但是使用strip b.exestrip -s b.exe并没有改变exe文件的大小。我需要指定一些额外的参数吗? - ben
@Damon:你说得完全正确,而且它们在使用--gc-sections链接时也可以节省空间,但在Windows上不行。所以就这个问题而言,它们什么也没做;-)。 - rubenvb
Mingw总是生成更大的可执行文件,我认为这无法解决(唯一能做的就是运行strip)。放弃吧。如果你想要更小的可执行文件和更快的编译速度,使用微软编译器。唯一的问题是微软编译器不太符合标准,并且有自己的小问题。 - SigTerm
显示剩余7条评论
4个回答

6
那些额外的字节被标准库调用“拉入”——高级别的调用往往会带来它们下面的所有内容,包括内存分配、它们使用的异常等等。开始时最容易的事情是尽量减少你所使用的内容。以 putchar() 为基础创建一个 hello world 程序可能会给你一个很好的比较点。我将专注于静态链接程序,因为这是我所知道的,rubenvb 的回答似乎已经很好地涵盖了共享库。

像 new、delete、pure virtual functions 等功能也会拉入库的一部分和许多依赖项。如何替换它们的快速概述在这里:http://ptspts.blogspot.com.au/2010/12/how-to-write-c-program-without-libstdc.html - 如果你变得非常极端,你可以找到一个类似处理 malloc 的版本。

最近,在 C++11 中,如果你这样做,你会遇到 __cxa_guard_acquire 和 __cxa_guard_release:

int foo() {
    static int bar = 1; //thread safe in C++11, requires inbuilt functions
}

如果您的编译器支持,可以使用-fno-threadsafe-statics标志。

如果这还不够,您可以在Linux版本的ld链接时使用-Map = filename.map标志生成“映射”文件。文件的第一部分列出了每个所需的部分以及引用该部分的部分。*

*地图文件还将显示,由于已经使用该标志对标准库进行了编译,因此函数节区对标准库无效,除此之外。


我也是在手机上写这篇文章,所以如果您能耐心等待,我会添加链接、格式和用户名。 - Matthew Elvey Price
到目前为止,这是最佳答案。博客文章的链接非常有趣。 - ben

2
没有办法摆脱MinGW GCC编译的可执行文件所需的额外代码。您可以尝试使用MinGW-w64工具链来处理CRT,但在“Hello World”大小方面可能比纯MinGW更糟糕。
如果二进制文件大小很重要,我强烈建议使用MSVC,因为它依赖于内置到操作系统中的自己的CRT代码。这在某种程度上可以被谨慎地称为“作弊” :-).
关于DLL libstdc++,当然是有可能的,只需使用一个好的工具链 ;-). 请查看此处。请注意,这也是“作弊”,因为我怀疑比赛将忽略运行时库的大小。这就是为什么代码大小竞赛很愚蠢的原因。你总是会忽略另一层次的运行时。直到你到达裸机内核或汇编语言,这不是该竞赛的重点。

我不知道你是否研究过这个问题,但我发现在msvc中编译的二进制文件比mingw小得多(97kb对643kb)。请注意,msvc 确实静态地链接其stdlib和crt。在此处测试演示。如果你检查cl编译的二进制文件的导入目录,你会看到它只包含 kernel32.dll,这证实了crt和stdlib已经被静态地链接到二进制文件中。 - greatwolf
另一方面,Mingw 的二进制文件导入了 msvcrt.dlllibwinpthread-1.dllkernel32.dll - greatwolf
@greatwolf 你可以使用 -static 链接选项将 winpthread 静态链接。然后二进制文件依赖于 ntdll.dll、msvcrt.dll 和 kernel32.dll。是的,它会变得更大半兆字节,但从什么时候起这种大小对于实际应用程序来说是如此重要呢?如果一个 Hello World 应用程序的大小对你来说如此重要,请使用 MSVC。是的,在 Windows 上,MSVC 比 GCC 更擅长删除死代码,并使用操作系统提供的功能。 - rubenvb

1

我尝试在QT 5.1.0 MinGW 4.8 32位下运行您的代码

#include "windows.h"
#include <string>
#include <iostream>
int main() {
    MessageBoxA(0, "test", "test", 0);


std::string asdf;

    return 0;
}

在发布模式下,exe文件的大小为34 kb,在调试模式下,exe文件的大小为92 kb。

这比我的gcc 4.8.1好多了,但仍然太大了。不过还是谢谢! - ben

1
尝试在链接时使用“-fwhole-program”选项,这可能是一种冒险的尝试。同时,在编译和链接时使用“-flto”也可能会有帮助。

好的,我已经尝试了两种方法。因为文档上是这么说的(http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Optimize-Options),无论是一起使用还是分开使用,可执行文件大小都**完全相同**。不过还是谢谢你! - ben

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