32

我最近才开始学习 C++ - 我在 Windows 上使用nuwen 的 MingW版本,使用 NetBeans 作为 IDE(我也有 MSVC 2008 的 MSDN AA 版本,虽然我不经常使用它)。

编译这个简单的程序时:

#include <iostream>
using namespace std;

int dog, cat, bird, fish;

void f(int pet) {
  cout << "pet id number: " << pet << endl;
}

int main() {
  int i, j, k;
  cout << "f(): " << (long)&f << endl;
  cout << "dog: " << (long)&dog << endl;
  cout << "cat: " << (long)&cat << endl;
  cout << "bird: " << (long)&bird << endl;
  cout << "fish: " << (long)&fish << endl;
  cout << "i: " << (long)&i << endl;
  cout << "j: " << (long)&j << endl;
  cout << "k: " << (long)&k << endl;
} ///:~

我的可执行文件大约有 1MB 大。当我将项目配置从Debug更改为Release时,使用 -O1 -Os 标志(沿途剥离调试符号),二进制大小从 1MB 减少到 544KB。

我不是“大小怪胎”,但我只是想知道 - 有什么办法可以减少 .exe 的大小吗?我只是认为,对于这样一个简单的应用程序来说,544KB 实在是太多了)。

4

14 回答 14

58

这里的问题不在于库,而在于
库的链接方式。诚然,iostream是一个中等规模的库,但我
认为它不会大到导致程序生成比使用函数
900KB的类似程序更大的可执行文件。C罪魁祸首
不是iostream但是gcc。更准确地说,static linking是责怪。

您将如何解释这些结果(使用您的程序):

g++ test.cpp -o test.exe              SIZE: 935KB
gcc test.cpp -o test.exe -lstdc++     SIZE: 64.3KB

使用完全相同的
构建选项生成不同大小的可执行文件。

答案在于gcc链接目标文件的方式。
当您比较这两个命令的输出时:

g++ -v test.cpp -o test.exe // c++ program using stream functions  
gcc -v test.c -o test.exe   // c program that using printf  

您会发现它们唯一不同的地方(除了
临时目标文件的路径)是在使用的选项中:

   C++(iostream) | C(stdio)
-------------------------------
-Bstatic         |  (Not There)
-lstdc++         |  (Not There)
-Bdynamic        |  (Not There)
-lmingw32        | -lmingw32 
-lgcc            | -lgcc 
-lmoldname       | -lmoldname 
-lmingwex        | -lmingwex 
-lmsvcrt         | -lmsvcrt 
-ladvapi32       | -ladvapi32 
-lshell32        | -lshell32 
-luser32         | -luser32 
-lkernel32       | -lkernel32 
-lmingw32        | -lmingw32 
-lgcc            | -lgcc 
-lmoldname       | -lmoldname 
-lmingwex        | -lmingwex 
-lmsvcrt         | -lmsvcrt 

你的罪魁祸首就在上面。-Bstatic
紧跟在目标文件之后的选项,它可能看起来像这样:

"AppData\\Local\\Temp\\ccMUlPac.o" -Bstatic -lstdc++ -Bdynamic ....

如果您使用这些选项并删除“不必要的”库,则可以 在我的情况下
将可执行文件的大小从最大减小934KB4.5KB最大。
4.5KB通过使用-Bdynamic,-O标志
和你的应用程序不能没有的最重要的库来获得这一点,即
-lmingw32, -lmsvcrt, -lkernel32. 此时您将获得一个25KB的可执行文件
。将其剥离至10KB并将其UPX4.5KB-5.5KB.

这是一个可以玩的 Makefile 文件:

## This makefile contains all the options GCC passes to the linker
## when you compile like this: gcc test.cpp -o test.exe
CC=gcc

## NOTE: You can only use OPTIMAL_FLAGS with the -Bdynamic option. You'll get a
## screenfull of errors if you try something like this: make smallest type=static
OPTIMAL_FLAGS=-lmingw32 -lmsvcrt -lkernel32

DEFAULT_FLAGS=$(OPTIMAL_FLAGS) \
-lmingw32 \
-lgcc \
-lmoldname \
-lmingwex \
-lmsvcrt \
-ladvapi32 \
-lshell32 \
-luser32 \
-lkernel32 \
-lmingw32 \
-lgcc  \
-lmoldname \
-lmingwex \
-lmsvcrt


LIBRARY_PATH=\
-LC:\MinGW32\lib\gcc\mingw32\4.7.1 \
-LC:\mingw32\lib\gcc \
-LC:\mingw32\lib\mingw32\lib \
-LC:\mingw32\lib\

OBJECT_FILES=\
C:\MinGW32\lib\crt2.o \
C:\MinGW32\lib\gcc\mingw32\4.7.1\crtbegin.o

COLLECT2=C:\MinGW32\libexec\gcc\mingw32\4.7.1\collect2.exe

normal:
    $(CC) -c test.cpp
    $(COLLECT2) -Bdynamic $(OBJECT_FILES)  test.o -B$(type) -lstdc++ -Bdynamic  $(DEFAULT_FLAGS) $(LIBRARY_PATH) -o test.exe

optimized:
    $(CC) -c -O test.cpp
    $(COLLECT2) -Bdynamic $(OBJECT_FILES)  test.o -B$(type) -lstdc++ -Bdynamic  $(DEFAULT_FLAGS) $(LIBRARY_PATH) -o test.exe

smallest:
    $(CC) -c -O test.cpp
    $(COLLECT2) -Bdynamic $(OBJECT_FILES)  test.o -B$(type) -lstdc++ -Bdynamic  $(OPTIMAL_FLAGS) $(LIBRARY_PATH) -o test.exe

ultimate:
    $(CC) -c -O test.cpp
    $(COLLECT2) -Bdynamic $(OBJECT_FILES)  test.o -B$(type) -lstdc++ -Bdynamic  $(OPTIMAL_FLAGS) $(LIBRARY_PATH) -o test.exe
    strip test.exe
    upx test.exe

CLEAN:
    del *.exe *.o

结果(YMMV):

// Not stripped or compressed in any way
make normal    type=static     SIZE: 934KB
make normal    type=dynamic    SIZE: 64.0KB

make optimized type=dynamic    SIZE: 30.5KB
make optimized type=static     SIZE: 934KB

make smallest  type=static     (Linker Errors due to left out libraries)
make smallest  type=dynamic    SIZE: 25.6KB 

// Stripped and UPXed
make ultimate type=dynamic    (UPXed from 9728 bytes to 5120 bytes - 52.63%)
make ultimate type=static     (Linker Errors due to left out libraries)

-Bstatic包含在默认构建选项中的一个可能原因
是为了获得更好的性能。我尝试构建astyle并平均降低-Bdynamic
1 秒的速度,即使应用程序
比原始应用程序小得多(UPXed 时为 400KB 与 93KB)。

于 2014-02-03T22:45:13.367 回答
24

#include <iostream>

导致许多标准库被链接,至少与 g++。如果您真的关心可执行文件的大小,请尝试将所有使用 iostreams 的内容替换为 printf 或类似内容。这通常会以方便和类型安全为代价,为您提供更小、更快的可执行文件(我将您的可执行文件缩小到大约 6K)。

于 2009-06-25T09:16:27.693 回答
11

不确定它对您有多大用处,但有人在减小简单 Windows .exe 的大小方面做了很多工作

通过使用一些非常极端的方法,他们能够创建一个简单的 .exe,它将在现代版本的 Windows 上以 133 个字节执行。

于 2009-06-25T09:08:08.677 回答
9

您可以使用 -s,我相信它也内置于 mingw 中。在 cygwin 上使用 g++ 3.4.4 编译的简单 hello world 应用程序生成了 476872 字节的可执行文件,再次使用 -s 编译(去除不必要的数据),将相同的可执行文件减少到 276480 字节。

cygwin 上使用 g++ 4.3.2 的同一个 hello world 应用程序生成了 16495 字节的可执行文件,使用 strip 将大小减小到 4608 字节。据我所知,可能最好使用更新版本的 g++。

MingW 刚刚发布了 gcc 4.4.0,所以如果可执行文件大小很重要,那么我会考虑使用它。正如它所表明的那样, -s 可能会帮助您去除大部分调试信息,仅在用于生产时才建议这样做。

于 2009-06-25T11:08:28.230 回答
8

你会得到 C++ 标准库和我猜的其他东西,因为 mingw 有自己的这些库的实现。

不用太担心,当你制作更复杂的程序时,大小不会相应增长。

于 2009-06-25T09:12:34.720 回答
4

基本上,您无法通过 mingw 的基本分布来减少 .exe 的大小。550kb 大约是你能得到的尽可能小,因为 mingw 和 gcc/g++ 通常不擅长剥离未使用的函数。其中大约 530kb 来自 msvcrt.a 库。

如果您真的想进入它,您可以使用 -ffunction-sections -fdata-sections 编译器选项重建 msvcrt.a 库,然后在链接您的应用程序时使用 -Wl,--gc-sections 链接器选项,这应该能够从那里剥离很多东西。但是如果你只是学习 C++,重建那个库可能有点高级。

或者您可以只使用 MSVC,它非常适合剥离未使用的功能。使用 MSVC 编译的同一段代码会生成一个 10kb 的 exe。

于 2009-06-25T09:50:40.430 回答
3

如果您需要小型可执行文件,Tiny C 将为 printf("Hello world!") 编译一个 1536 字节的可执行文件 TinyC 只是 C,而不是 C++,并且已知编译速度比 gcc 更快,可执行文件速度更慢。

编辑:我刚刚尝试了一个 cout<"Hello World!" 在 DevC++(捆绑了 Mingw 4.8 和一个 Ide)中,我得到了一个 4.5 MB 的可执行文件!

于 2013-03-18T15:27:14.070 回答
2

好吧,当您使用 C++ 标准库时,exe 可以很快变大。如果在剥离调试符号后,您仍想减小软件的大小,您可以使用像UPX这样的打包程序。但是,请注意,由于某些病毒很久以前就使用过 UPX,因此某些防病毒软件会阻塞带有 UPX 的 exe。

于 2009-06-25T09:16:21.170 回答
2

在你创建它之后,你总是可以在你的 exe 上运行UPX 。

于 2009-06-25T09:16:21.733 回答
2

我使用 Cygwin 和 g++ 复制了您的测试。您的代码使用 -O2 编译为 480k。在可执行文件上运行strip将其减少到 280k。

不过,总的来说,我怀疑您的问题是使用了 <iostream> 标头。这会导致一个相当大的库被链接。另外,请注意,cout << x它不仅仅是打印。有语言环境和流以及各种底层的东西。

但是,如果具有较小的可执行文件大小是一个真正的关键任务目标,那么请避免它并使用 printf 或 puts。如果不是,那么我会说支付 iostream 的一次性费用并完成它。

于 2009-06-25T10:05:49.823 回答
2

如果您使用“nm”实用程序或其他显示 .exe 内容的程序,您会看到它包含大量有人可能想要使用的类,但您没有。

于 2009-06-25T20:29:21.060 回答
1

像 msvc8 这样的其他编译器,甚至像 borland c++ 5.5.1 这样的 order 编译器,为什么能够生成非常小的可执行文件,而 mingw gcc 却不能呢?

我为以下每个工具集快速编译了一个“hello world”,并观察了编译后的可执行文件大小。请注意,在所有这些情况下,运行时库都是静态链接的,并且所有调试符号都已被剥离:

compiler toolchain            exe size                   exe size
                              (w/iostream header)        (w/cstdio printf)
-------------------------------------------------------------------------
Borland C++ 5.5.1             110kbyte                    52kbyte
MSVC 2008 express             102kbyte                    55kbyte
MinGW- GCC 3.4.5              277kbyte                    <10kbyte
MinGW- GCC 4.4.1              468kbyte                    <10kbyte

有趣的是,更高版本的 gcc 4.4.1 产生了比 gcc3.4.5 更大的可执行文件,这可能是由于 libstdc++ 的不同版本。

那么在mingw的链接阶段真的没有办法删除死代码吗?

于 2010-02-12T07:09:19.550 回答
0

规模的主要部分源于使用相当广泛的运行时库。所以在现实生活中,如果你有这样一个简单的应用程序,你实际上是在链接一个非常大的“死代码”。

据我所知,没有链接器标志可以跳过链接库的未使用部分。

我知道有两种方法可以伪造一个较小的应用程序:

  1. 使用动态链接。然后您的应用程序引用动态加载的库。您仍然需要完整大小(实际上更多),但您的可执行文件要小得多。
  2. 使用可执行的压缩系统。
于 2009-06-25T09:21:51.067 回答
0

答案很晚,但可能对在此处结束搜索“减少 hello world 二进制大小”的新 c++ 用户有所帮助:

这是我hello_world.exe用~20KB编译a的步骤:

hello_world.cpp

#include <cstdio>  // not <iostream>
int main()
{
 printf("Hello, World");
 return 0;
}

g++ 命令行

g++ -s hello_world.cpp -o hello_world.exe

UPX将二进制文件减少约 50%:

upx -9 hello_world.exe

      File size         Ratio      Format      Name
 --------------------   ------   -----------   -----------
   40448 ->  20992      51.90%    win64/pe     hello_world.exe   
于 2020-04-24T03:36:09.430 回答