C, Programming, Uncategorized

Optimize your C executable/plugin with GCC/Strip

I have encountered a lot of users who do not know how to properly optimize their C programs and plugins. For this reason, people prefer older (and possibly inefficient/unsafe) compilers. I hope this article will allow users to use GCC (the industry standard) better. It may improve the efficiency and reduce the size of your compiled executables. I will discuss in brief how a compiler works, as well as how to reduce the size of your executable, and tips on avoiding increasing their size.

How does a compiler work?

A compiler takes in your source code (C, or any compiled language) and translates it into executable machine code. No programming language is directly executable by the computer. In the case of shell languages like Batch, a existing program is used to “interpret” the language and processes the operations. In a compiled language, a program looks over the source code, and emits machine code which is stored in a executable format (like an EXE).

Compilers also optimize your code. This can result in your program being faster, smaller or more efficient. A important thing to remember is that efficiency is not necessarily speed. A program may be fast, but it may lack important things such as error protection, sorting efficiency or security. This is why, when developing public projects, you must keep in mind of those three things when choosing a compiler as well as your design patterns.

Why is my executable big?

There are a few things that can increase the size of your executable file, I will list some of them below:

  1. Libraries – libraries are a huge culprit for file size. Never include libraries that you aren’t going to use, and keep in mind that standard libraries can be big. If you only need to use one simple function in a math library, try and implement it yourself.
  2. Embedded Resources – Almost every compiler by default embeds debug information into your executable (line numbers, error readouts and even entire programs that you did not intend to include). If your program has proper error protection, it is wise to remove this in your final build. It can reduce files that were once 100-200kb down to 20-30kb. The same applies to embedded resources such as other files, images, etc.
  3. Static Linking – This is a very advanced topic, however a lot of libraries that are embedded in your C application may not need to be embedded in the first place, resulting in large file size. This is a concept called “Dynamic Linked Libraries”. I encourage you to read a introduction of it here.
  4. Compiler Flags – Standard compilers like GCC have optimization flags you can set when you compile a program. I will discuss a few of them below, however they are a very large set of flaga and I encourage you to read the official manual here

Reduction Method 1 – Compiler Flags

If you have a C program that simply prints “Hello, World!”, you will likely have a single .C file that looks like this:

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("hello, world!");
    return 0;
}

If we run the standard “gcc helloworld.c” on this program, we will get a emitted binary (a fancy term for a executable we just made in our compiler) that is around 70kb. This is very large, and it’s expected that some people will blame inefficiencies in the compiler for it. However, it is a human error! The compiler was simply doing what it was told with the information it got. “Compile the program, do not use any special exceptions to your behavior, and embed debug information since it’s the default”.

Let’s try that again, except let’s do “gcc -Os helloworld.c”. -Os is a compiler flag that “optimizes for size“. The compiler will attempt to reduce the size of the executable as much as it can. In my development setup, this typically removes around 5-6KB of the file size with this flag. It is not a lot, but it is an improvement.

A drawback of optimizing for size like this is that small size means less efficiency in some programs. The compile will favor “size at all costs” and may mean that your program would be slower than you expect it to be. In most plugins, this could be a matter of only 1/2 a millisecond or so. However, in plugins intended to be used thousands of times a minute it could be noticeable.

I would say that most applications within the range of 10-100kb do not have any noticeable size-caused speed changes. Most of the speed reduction is caused by program design, and a lot of times programs are faster when run more than once as they are cached.

Reduction Method 2 – Strip

For reasons unknown to me, GCC does not seem to have implemented the ability to strip unused/dead code in the compiler flags. This means that GCC is compiling executable code in libraries we include (like STDIO, and STDLIB) into our file. Even worse, GCC by default will include debug information (useful for developers, not for a final product) that will drastically inflate the file size of our application. A simple solution to this is the strip command that is available in whatever environment you installed GCC in (be that Cygwin, MSYS, etc). Let’s run the strip command on our file simply by doing “strip a.exe” (a.exe can be whatever filename our executable is).

The file size for our “Hello World!” program was reduced from ~65kb all the way down to 9kb. That is almost ridiculously tiny! Strip simply removes all unused code and symbols (including debugging information) from our executable after it is compiled, thus we are left with just the bare minimum executable code.

Reduction Method 3 – Executable Compression

The third method for reducing your file size has benefits and drawbacks. You can compress your EXE with something like UPX, and it may reduce the file size a lot. This is especially so if your program contains embedded resources like images or data files. The drawback of compression is of course, the decompression latency. This is the time it takes for the executable to decompress on runtime. A compressed executable cannot run, so it must be decompressed. The average latency for a executable like this is around 200-300 milliseconds. This, however, can vary depending on your platform as well as the OS’ ability to cache your executable.

There are a variety of other tools similar to UPX that you can use. However I have not done much research on them and can’t comment on their differences. I would encourage you to research them on your own, and figure out what fits your needs.

Conclusion

I hope that these three methods will help you in creating efficient, and small programs with GCC. The first two methods are by far the largest culprit in applications I have seen made in the compiler. Compilers and build tools are a very complex topic, so it’s understandable that you may have more questions. There are a lot of YouTube and written explanations on how they function for a variety of languages. If you have any additional suggestions, consider commenting below and I will reach back to you. Thank you for reading this article.

2 thoughts on “Optimize your C executable/plugin with GCC/Strip

    1. This is a good point thank you for commenting. However, I would still consider it a valid option. You can see on the UPX GitHub that they are working on combatting false positive reports 🙂

Leave a Reply