How to Reduce the Size of Executable Produced by Mingw G++ Compiler

How to reduce the size of executable produced by MinGW g++ compiler?

Flags to use:

  • -s like you've been doing to strip symbols
  • -lstdc++_s to specify dynamically linking against the libstdc++.dll
  • -Os to optimize the binary for size.

By default mingw static links to libstdc++.a on Windows.

Note that the lstdc++_s flag is only in MinGW with GCC > 4.4, I believe.

GCC C++ Hello World program - .exe is 500kb big when compiled on Windows. How can I reduce its size?

The problem here is not so much with the library as it is with the way the

library is linked. Granted, iostream is a moderately huge library but I don't

think it can be so huge as to cause a program to generate an executable that is

900KB larger than a similar one that uses C functions. The one to blame

is not iostream but gcc. More accurately, static linking is to be blamed.

How would you explain these results(with your program):

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

Different sizes of executables are being generated with exactly the same

build options.

The answer lies in the way gcc links the object files.

When you compare the outputs from these two commands:

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

you'll find out that the only places they differ(apart from the paths to the

temporary object files) is in the options used:

   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

You've got your culprit right there at the top. -Bstatic is the option that comes

exactly after the object file which may look something like this:

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

If you play around with the options and remove 'unnecessary' libraries,

you can reduce the size of the executable from 934KB to 4.5KB max

in my case. I got that 4.5KB by using -Bdynamic, the -O flag

and the most crucial libraries that your application can't live without, i.e

-lmingw32, -lmsvcrt, -lkernel32. You'll get a 25KB executable at that

point. Strip it to 10KB and UPX it to around 4.5KB-5.5KB.

Here's a Makefile to play with, for kicks:

## 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

Results(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)

A possible reason for the inclusion of -Bstatic in the default build options

is for better performance. I tried building astyle with -Bdynamic and got

a speed decrease of 1 second on average, even though the application was way

smaller than the original(400KB vs 93KB when UPXed).

How to reduce exe size produced with CodeLite mingw 4.7.1

The size of the executable is not related to the Makefile, but because of the inclusion of iostream (removing it will reduce your exe to the minimum)

However you might want to add '-s' to the linker options from: project settings -> common settings -> linker

adding '-s' will reduce the executable by half to around ~400KB in release mode.
You can also try and run 'strip' on the executable

Eran

Why is smallest compiled exe I can make with GCC is 67KB?

I just tried this in x86_64 Linux, which probably isn't much different to MinGW at this level, although you never know.

Basically, the problem is that, even though nothing gets pulled in from the C library unless it's referenced, the CRT "startfiles" do reference a small selection of things, which in turn reference some other things, and "Hello world" ends up looking bad. This is not a problem worth fixing because all real programs would reference those core functions anyway.

The source for the start files is available, and quite small, and the compiler allows you to override the standard ones if you choose to, so optimizing them is not a massive deal. They're written in assembler code, but you can probably remove most of the extraneous garbage by simply deleting lines.

But, there's a hack for cutting the start-files out of the equation altogether:

#include <unistd.h>

void _start (void) {
write(1,"Hello world!", 12);
_exit(0);
}

Compile: gcc -nostartfiles t.c -s -static

Which works (by chance, see below), and gives me a file size of 1792 bytes.

For comparison, your original codes gives 738624 bytes, with the same compiler, which drops to 4400 bytes when I remove -static, but then that's cheating! (My code actually gets larger without -static, because the dynamic linker meta-data outweighs to code of write and _exit).

The by chance part, is that the program now has no stack pointer initialized. Likewise for all other global state the start-files usually take care of. As it happens, on x86_64 Linux, this isn't a fatal problem (just don't do it in production, right?) However, when I tried it with -m32 I get a segmentation fault inside write.

The problem can be fixed by adding your own initialization for that stuff, but then the code would no longer be as portable (it isn't absolutely portable already). Alternatively, call the write system call directly.

Code::Blocks + MinGW: minimize the size of a static library

The following will reduce the size of your compiled objects (and thus the static library)

-Os -g0 -fvisibility=hidden -fomit-frame-pointer -mno-accumulate-outgoing-args -finline-small-functions -fno-unwind-tables -fno-asynchronous-unwind-tables -s

The following will increase the size of objects (though they may make the binary smaller)

-ffunction-sections -fdata-sections -flto -g -g1 -g2 -g3

The only way to really make a static library smaller is to remove the unneeded code by compiling the static library with -ffunction-sections -fdata-sections and linking the final product with -Wl,--gc-sections,--print-gc-sections to find out what parts are cruft. Then go back and remove those functions/variables (this also works for making smaller shared libraries - see http://libraryopt.sf.net)

Optimization level in GCC for reducing executable size

Think of the Optimization Levels as a guideline style for the compiler, they will never guarantee the smallest or fastest executable but will build the code in a way best suited the the level of optimization you are aiming for.

0s will not test all levels then pick the smallest executable, it will just build using rules that tend towards smaller executable sizes rather than faster execution for example.

Add a few loops to your program so it has more options for the compiler then you might start to see some more significant differences. If you really want to understand the differences in Optimization Levels then start disassembling the executable and see what assembly code the compiler produced and compare the various styles.

MinGW gcc vs linux gcc executable size

According to the MinGW wiki, debugging information can be included from the libraries linked with your executable. You can exclude the debugging information from your executable, with gcc "-s" option or the "strip" command.

  • http://www.mingw.org/wiki/Large_executables

Support for "Gnu style" or "MS style" printf format seems to be one of the differences between MinGW stdio and the GNU C library stdio.

  • MinGW https://sourceforge.net/p/mingw/mingw-org-wsl/ci/5.0-active/tree/mingwrt/include/stdio.h
  • MinGW-w64 https://github.com/msys2/mingw-w64/blob/master/mingw-w64-headers/crt/stdio.h
  • GNU C Library https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=libio/stdio.h;hb=HEAD

MinGW

#>  gcc --version
gcc.exe (GCC) 5.3.0

#> gcc -o printf_gcc_mingw.exe printf.c
#> gcc -s -o printf_gcc_strip_mingw.exe printf.c
#> du -ch *.exe
59K printf_gcc_mingw.exe
45K printf_gcc_strip_mingw.exe

MinGW-w64

#>  gcc -o printf_gcc.exe printf.c
#> gcc -s -o printf_gcc_strip.exe printf.c
#> gcc --version
gcc (tdm64-1) 5.1.0
#> dir *.exe
132,613 printf_gcc.exe
16,896 printf_gcc_strip.exe
#>objdump -x printf_gcc_strip.exe
DLL Name: msvcrt.dll
vma: Hint/Ord Member-Name Bound-To
85ca 55 __C_specific_handler
85e2 78 __dllonexit
85f0 81 __getmainargs
8600 82 __initenv
860c 83 __iob_func
861a 91 __lconv_init
862a 97 __set_app_type
863c 99 __setusermatherr
8650 116 _acmdln
865a 123 _amsg_exit
8668 141 _cexit
8672 252 _fmode
867c 330 _initterm
8688 438 _lock
8690 610 _onexit
869a 820 _unlock
86a4 1031 abort
86ac 1049 calloc
86b6 1062 exit
86be 1081 fprintf
86c8 1088 free
86d0 1099 fwrite
86da 1146 malloc
86e4 1154 memcpy
86ee 1163 printf
86f8 1184 signal
8702 1205 strlen
870c 1208 strncmp
8716 1240 vfprintf

Visual Studio 2015

#>  cl /MD printf.c /link  /out:printf_vs.exe
#> dir *.exe
9,728 printf_vs.exe

#>printf_vs.exe
0123456789


Related Topics



Leave a reply



Submit