This article presents additional flags you can specify to the compiler in order to customize the binary ouput and customize the behavior of the compiler.
Specifying Compiler and Linker flags
As I mentioned in part 1, we need to configure the compiler with more options, according to our requirements or needs. In this part, I will show the most common and useful directives and configurations in order to complete the compilation process as we require. There are many other flags which can be used for application specific requirements. These flags are documented in the GCC online documents site (see resources).
Specifying include directories
In most cases, we need to include header files which are not provided by the system. Such header files could be from other modules or libraries that we want to use. In this case, we are going to specify the –I directive. This directive may contain a long list of include directories. In case we want to specify a secondary location (another include directory that if a primary and a secondary locations have the same file, the compiler will take the one from the primary), we use the –idirafter directive.
Here’s an example:
|# gcc –c mysource.c –o myobject.o –I/usr/project/include –idirafter /usr/project/sec/include|
Adding debug symbols to the generated object
Debug symbols are required in case we want to use a debugger. These symbols can be used by GDB, a hardware debugger or one of the binutils for information extraction. Debug symbols make the object bigger due to the extra debug information which is embedded inside the object. However, when creating a final filesystem image for the target, all the executables are usually stripped from this information, which makes their size to be equivalent to their original non-debug size. The following example shows how to instruct the compiler to add debug symbols:
|# gcc –c mysource.c –o myobject.o –g -ggdb|
Setting the warnings level
If you are a good programmer, you would like the compiler to warn you about everything, because almost every warning has a potential to be a real bug, and fixing them when they are young is the easiest and cheapest. Fixing something which already runs in the field is extremely expensive, not to mention the bad reputation. The default warning level used by gcc reports only syntax errors, but it won’t report other potential issues. Here are some directives to instruct the gcc regarding warnings:
- -fsyntax-only – Check only syntax errors (default).
- -pedantic – Generate warnings demanded by ANSI-C requirements.
- -Wwarning-type – Enable a specific warning (from the list).
- -Wno-warning-type – Disable a specific warning.
- -w – Disable warnings completely, dangerous!
- -Wall – Enable all warning types, recommended!
- -Werror – Treat all warning as errors.
My recommendation is to add the –Wall flag to the compilation process and fix all outstanding issues. The –w flag is dangerous because it will inhibit the compiler from reporting any issue (including real bugs). The –Werror flag is like a police officer mode. Once your code is stable and clean from warnings, you can enable this flag. Any changes that you or your team will do will not compile in case a new warning was introduced. This will enforce code quality (in terms of warnings only), but will make the programmers life hard, because it will fail the compilation for every nonsense. The gcc manual displays many warning types that can be disabled or enabled manually. There is no actual use for turning on or off an explicit warning, however, an example for this could be a case where you don’t want to see a specific warning type on a 3rd party code that you can’t modify.
Defining and Undefining macros
What do we do in case there is an #ifdef which disables a required piece of code? What do we do in case the code expects a value in a macro and no one specifies it? How can we do that without changing the code? The answer is, by using the –D and
–U directives. The first defines a macro and the latter undefines a macro. Here are some examples:
|# gcc –c mysource.c –o myobject.o –DMAX_ARRAY_SIZE=16|
This will be useful in case our program defines an array, but doesn’t know of what size. The outsize definition will define the size and the code will support 16 places in the devices array:
|unsigned int devices[ MAX_ARRAY_SIZE ];|
In this example, we change the way the program works:
|# gcc –c mysource.c –o myobject.o –DENABLE_USB_DEVICE|
The code supports USB device, but by default this option is off. We use the command line definition to enable this code.
|int setup_devices( void )
int ret = 0;#ifdef ENABLE_USB_DEVICE
ret = usb_init();
#endif ret |= eth_init(); return ret;
Setting the optimization level
Believe it or not, but the compiler’s objective is to minimize the work and memory consumption on the host, and it doesn’t care about the target! This is absurd, because we really don’t care about the host machine, which is a strong and powerful PC or server, but we do care about our resource limited target platform. For this reason, we must specify the optimization level we want to use on our target. Here are the optimizations levels:
- ‘-O0’: Do not optimize. Could be dangerous – even though it looks harmless, this optimization level is known to break programs that are working correctly without it. The main usage of this level is to allow convenient step-by-step walk of the code with a debugger. In case your program crashes when compiled with this level, try the second optimization level.
- ‘-O1’: Optimize. Try to reduce code size and execution time. This is a good alternative for debug in case level 0 doesn’t work.
- ‘-O2’: Optimize even more. GCC performs nearly all supported optimizations that do not involve a space-speed tradeoff. It is my recommendation to use this option for your RT Embedded project.
- ‘-O3’: Optimize yet even more. Perform optimization that involves a space-speed tradeoff, output will be larger. Could be dangerous – It also makes assumptions about functions and variables that sometimes you may not be aware of (for instance, volatiles).
- ‘-Os’: Optimize for size. `-Os’ enables all `-O2′ optimizations that do not typically increase code size. It also performs further optimizations designed to reduce code size.
When you debug something, you usually edit a file and want to rebuild and test. In this case, you’ll want that the system will compile only the file you changed, and nothing else, because building the whole project usually takes a lot of time. That’s why we have dependency files. Dependency files (usually with the extension of “.d”) contain a recursive list all the files that a source file includes. As you know, in case one of them is changed, there is a need to recompile this source file. Dependency files are used by Makefiles in order to determine whether a source file needs to be recompiled or not. This helps to speed up the build process of a project starting from the second time it is built, by skipping work which is not required. The generated dependency files are not used by the gcc, but only generated by the gcc. There are a few different flags which generate dependency files. My recommendation is to use the –MMD flag which generates the list of header files (without system headers which are not supposed to be changed). The following example will generate mysource.d:
|# gcc –c mysource.c –o myobject.o –MMD|
Specifying library search directories
Similarly to the include directory specification, you may want to tell the linker where to find all the libraries that your program requires. This is done by the –L flag. See the example in the next section.
Specifying a library for the final link
When your program is using external functionality which is provided by a library, you must specify the library’s name in the final link command line. This is done by the –l flag, and the library’s core name. When I say “core name”, I mean that you need to specify the library name without lib prefix and the extension. Here’s an example of linking with a library called libmylib.so or libmylib.a. The linker will link with the latter in case both exist.
|# gcc myobj1.o myobj2.o myobj3.o –o mybigprog –L/usr/lib -lmylib|
In case you need to link with an archive (a static library), you can also specify its name in full path. When multiple archives are used for the link, and in case there are cross dependencies between them, you must direct the linker to keep all the archive open for function resolving until all are resolved. Otherwise, you’ll get link errors for unresolved functions, even though you specified the library’s name correctly. This is done by defining a group:
|# gcc myobj1.o myobj2.o myobj3.o –o mybigprog –L/usr/lib –start-group –lmylib1 –lmylib2 –lmylib3 –end-group|
It’s the best practice to surround your list of libraries (either static or shared) with the group directive in all cases.
Sending commands to the linker
You can instruct gcc to send commands directly to the linker. These commands are not understood by the compiler itself, and passed as they are. The way it is done is using the –Wl,command directive. Usually, you don’t have to use this directive. However, it can be used to further optimize the output. This will be described in an advanced article.
Creating a Map file
Map file is a detailed text file which contains lists of:
- Variables and their addresses
- Functions and their addresses
- Linker script
- Memory map
- Shared libraries
The map file is important and could help you locate and isolate crashes. The map file is created by sending a command to the linker as follows:
|# gcc myobj1.o myobj2.o myobj3.o –o mybigprog -Wl,-Map,myprog.map|
|Check out the ads, there could be something that may interest you there. The ads revenue helps me to pay for the domain and storage.|