【问题标题】:GCC C++ "Hello World" program -> .exe is 500kb big when compiled on Windows. How can I reduce its size?GCC C++ "Hello World" 程序 -> .exe 在 Windows 上编译时有 500kb 大。我怎样才能减小它的大小?
【发布时间】:2010-11-05 18:42:30
【问题描述】:

我最近刚开始学习 C++ - 我在 Windows 上使用 nuwen's 版本的 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 实在是太大了)。

【问题讨论】:

  • 完全不相关,但您应该使用 reinterpret_cast 或 static_cast 而不是 C 风格的演员表。
  • -O1 -Os 是多余的。只使用最后一个 -O 选项。
  • 当我的“Hello, World”程序太大时,我将它们优化为“Hi, World”。
  • 当我编译这个程序时,删除#include 并添加#include ,用printf替换cout,用“\n”替换endl - 编译后的exe将它的大小从544KB减少到6KB!我很惊讶。如果我不需要 提供的所有功能,可以这样编写 c++ 程序吗?
  • 这是一个“hello, world”程序吗?

标签: c++ optimization gcc netbeans size


【解决方案1】:

这里的问题不在于图书馆,而在于它的方式
库已链接。当然,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 ....

如果您使用这些选项并删除“不必要的”库,
您可以将可执行文件的大小从 934KB 减小到 4.5KB max
就我而言。我通过使用-Bdynamic 得到了4.5KB-O 标志
以及您的应用程序离不开的最重要的库,即
-lmingw32-lmsvcrt-lkernel32。您将获得一个 25KB 可执行文件
观点。将其剥离到 10KBUPX4.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 的可能原因
是为了更好的表现。我尝试用-Bdynamic 构建astyle 并得到了
平均速度降低 1 秒,即使应用程序很慢
比原始版本更小(400KB 对比 UPX 后的 93KB)。

【讨论】:

    【解决方案2】:

    #include <iostream>
    

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

    【讨论】:

    • 在 C 程序中,我没有包含任何包含文件,只有 main() 有一个变量定义。即使这样,文本段的大小也是 10628。所以这意味着在内部添加了一些东西。它们是什么?
    【解决方案3】:

    不确定它对你有多大用处,但someone has done quite a lot of work on reducing the size of a simple Windows .exe

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

    【讨论】:

    【解决方案4】:

    您可以使用 -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 可能会帮助您去除大部分调试信息,仅在用于生产时才建议这样做。

    【讨论】:

    • 您是否真的尝试过使用 std::cout 的示例?我认为这对最终的 exe 没有任何影响,它只是意味着您可以在源代码中键入一个较短的名称。
    • 这更可能是 cygwin 与 mingw 的区别,而不是 gcc 3.x 与 4.x。
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

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

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

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

      【讨论】:

      • FWIW GCC 4.5(在我的例子中是 MinGW)提供了一个 10kb 的文件,可能是链接时间优化 (gcc.gnu.org/onlinedocs/gccint/LTO.html)
      • 我尝试了 lto,但几乎没有改善 (10%)。它可能不像只是添加额外的选项那么简单
      【解决方案7】:

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

      编辑: 我刚刚尝试了一个 cout

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          您可以在创建 exe 后始终在其上运行 UPX

          【讨论】:

            【解决方案10】:

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

            【讨论】:

              【解决方案11】:

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

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

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

              【讨论】:

                【解决方案12】:

                其他编译器如 msvc8 甚至是 order 编译器如 borland c++ 5.5.1 为何能够生成非常小的可执行文件,而 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的链接阶段真的没有办法删除死代码吗?

                【讨论】:

                • 您确定您没有将 MSVC 与 MinGW 混淆吗? MSVC 会生成 10kb 的 exe。
                【解决方案13】:

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

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

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

                1. 使用动态链接。然后您的应用程序引用动态加载的库。您仍然需要完整大小(实际上更多),但可执行文件要小得多。
                2. 使用executable compression 系统。

                【讨论】:

                  【解决方案14】:

                  答案很晚,但可能对在此处结束搜索“reducing hello world binary size”的新 c++ 用户有所帮助:

                  这是我编译 hello_world.exe 大约 20KB 的步骤:

                  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   
                  

                  【讨论】:

                  • TCC 中的 C hello 字为 2 Kb,并且不使用可执行压缩器..
                  猜你喜欢
                  • 2012-10-28
                  • 2020-05-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-12-02
                  • 2015-05-15
                  • 2015-11-08
                  • 1970-01-01
                  • 2021-12-21
                  相关资源
                  最近更新 更多