Skip to content

The GCC Command Line – Part 3

July 11, 2011

Introduction

In the first article in this series, I looked at the basic use of the GCC C++ compiler’s command line, and in the second at using it to support modular software development. In this last article, I want to look at using the command line to support debugging and code optimisation.

The GDB Debugger

Along with the C++ compiler, and the other tools already described in this series, GCC comes with its own symbolic debugger, called GDB. If you installed the GCC suite using these instructions, you should be able to run it from the command line:

$ gdb
GNU gdb (GDB) 7.1
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "mingw32".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
(gdb)

The (gdb) at the end is the debugger’s prompt – you work the debugger by typing in commands like this:

(gdb) print 1 + 2
$1 = 3
(gdb)

This isn’t very exciting, so we’ll exit the debugger by typing "quit" (you could also type "q", or just hit Ctrl-C) and start it again, this time telling it the the name of the executable we want to debug (we’ll use the "hello world" program from the previous articles):

$ g++ hello.cpp -o hello
$ gdb hello.exe
[copyright stuff snipped for brevity]
Reading symbols from c:\users\neilb\home\temp\cline/hello.exe...done.
(gdb)

The debugger has loaded the executable and is ready for us to do some debugging. Unfortunately, if we do something relatively simple, like try to list the program:

(gdb) list

We get some rather incomprehensible error messages. This is because we compiled the program without debug information. The debug information, which the debugger needs in order to debug at the C++ level, consists mostly of the source code, mapped to particular machine code instructions.

Although we don’t have have the debug information, we can still do some useful stuff with gdb – for example, you can disassemble the program’s machine code and debug at the assembly language level:

(gdb) disas main
[produces listing of main() in assembler]

Most people will be more comfortable working at the C++ level, however, so we need to add the debug information. The GCC option to do this is -g:

$ g++ -g hello.cpp -o hello

If we now restart GDB, we can list the C++ source code and do useful things like setting breakpoints:

$ gdb hello
[copyright snipped for brevity]
(gdb) list
1       #include <iostream>
2       using namespace std;
3       int main() {
4               int x;
5               cout << "x is " << x << endl;
6       }
(gdb) b 4
Breakpoint 1 at 0x401352: file hello.cpp, line 4.
(gdb)

It is not my intention to provide here a tutorial on GDB – the GDB documentation is pretty good in this regard. One thing I should point out is that including debug information has no effect on the performance of your application, although it will significantly increase the size of the executable. If you want to remove the debug info, you can use GCC strip command to remove it without having to recompile everything without the -g option:

$ strip hello.exe

Remember:

  • Use the -g option to add debug information to your executable.

Optimisation

The GCC compiler has a very powerful code optimiser, which can produce code that rivals (and sometimes exceeds) in quality the best that could be written by a highly proficient human assembly language programmer. But by default, the optimiser is not turned on, and the code produced is similar to that which would be written by a competent, but rather literal-minded, human programmer. We’ll have a look at some code produced without optimisation, and then see what the optimiser can do.

To illustrate optimisation, we’ll write a small program that uses a loop, and call it loop.cpp:

#include <iostream>
int main() {
    int total = 0;
    for ( int i = 0; i < 42; i++ ) {
        total += i;
    }
    std::cout << total << std::endl;
}

Now, we’ll compile it without optimisation and examine the results using gdb:

$ g++ loop.cpp -o loop

Note there is no need to use the -g option, because we are only going to use gdb to disassemble the machine code. The code for the loop looks like this:

0x00401352:    mov    DWORD PTR [esp+0x1c],0x0     // set up loop control
0x0040135a:    mov    DWORD PTR [esp+0x18],0x0
0x00401362:    jmp    0x401370                     // execute loop at all?
0x00401364:    mov    eax,DWORD PTR [esp+0x18]
0x00401368:    add    DWORD PTR [esp+0x1c],eax
0x0040136c:    inc    DWORD PTR [esp+0x18]         // add to total
0x00401370:    cmp    DWORD PTR [esp+0x18],0x29    // at end? 0x29 == 41
0x00401375:    setle  al
0x00401378:    test   al,al
0x0040137a:    jne    0x401364                     // go round again

I’m not going to go into the code in detail, but I’ve added a few comments so you can see what is going on. In fact, it’s remarkably similar to the C++ code. Now let’s see what happen when we optimise:

$ g++ -O2 loop.cpp -o loop

and examine the assembler code for the loop:

0x00401368:    mov    DWORD PTR [esp+0x4],0x35d

Well, the first thing to notice is that there is no loop! The optimiser has understood what we were trying to do and removed the loop altogether, replacing the value it calculates with a hexadecimal constant, 0x35d, which is decimal 861, the value our program prints out. This is pretty impressive, but the compiler can actually do better – if we remove the output to cout statement, the optimiser will remove the body of main altogether!

This ability to produce very efficient code comes at a price. We’ve only looked at a very simple example here, but for real-world programs the optimiser can (and probably will) produce code that bears very little relationship to the structure of the C++ source. This makes it hard to debug optimised code, something I’ll discuss briefly a bit later.

Optimiser Options

As optimisation is one of the most important things a compiler does, GCC has a lot opf options for controlling the details of the optimisation process. However, it also provides several portmanteau options which handle the messy details for you – these are the ones you should normally use:

-O0 Don’t optimise – this is the default.
-O1 Perform some conservative optimisations, hopefully reducing code size and execution time
-O2 More aggressive optimisations – slows up compilation time significantly
-O3 Very aggressive optimisation – the output code will probably be incomprehensible
-Os Apply optimisations that reduce the size of the final executable, at the possible cost of performance

Remember:

  • Use -O otions to make the compiler optimise your code.

Debug and Release Builds

Which option you use is up to you; most people develop with no optimisations and then build for release with something like -O2. Note that it is crucial that you have a series of tests you can apply to the release build, as optimisation can introduce strange bugs and unexpected behaviour. Some people never do an an optimised build – if your application is heavily dependent on input/output, for example, there is probably no need to optimise it at all.

I’ve use the terms "debug build" and "release build" here, and they are commonly used in the software development industry, but it’s important to realise they have no meaning as far as GCC is concerned! GCC only knows about including debug information or performing optimisations – it never writes any special "debug code" and it is perfectly possible (though often not very useful) to have debugger information generated by the -g option in en executable which has been optimised with to the -O3 level! Debug and release builds are purely features of your build environment (IDE, make, whatever) and GCC knows nothing about them.

Conclusion

That about wraps up this third and final tutorial in the series. I hope they have been useful to you. Remember, GCC has a lot more options that haven’t been covered here, and you should at least flick through the GCC documentation to get an idea of what is available.

Advertisements
Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: