Skip to content

The GCC Command Line – Part 2

July 9, 2011

Introduction

In the previous article in this series, I looked at how to go about using the GCC compiler suite’s command line to compile a single source code file to produce an executable. In this article, I show you how to compile and link multiple files with GCC.

The Compilation Process

Before getting to compiling multiple files, it is useful to know what is happening when we compile a single source file:

$ g++ hello.cpp -o hello

Recall that g++ is not actually the C++ compiler – it is a driver program that hides a lot of the complexity of the compilation process from us. What is actually going on here is:

  • The C++ preprocessor is called to handle things like #include.
  • Preprocessed text is passed to the actual C++ compiler, which produces compiled assembly language code.
  • The assembly language code is assembled by the GCC assembler, producing an object code file.
  • The object code file is linked with the C++ libraries by the linker to produce the final executable.
  • The intermediate files are disposed of.

You can actually stop this process at any point, and examine the output of a particular step. It’s quite common to do this, when tracking down obscure bugs. For example, you can get GCC to show you the result of preprocessing:

$ g++ -E hello.cpp

This will produce absolutely reams of output, as the iostream header file we include in hello.cpp is a very big one, so I won’t show the output here. You can also examine the assembly language output:

$ g++ -S hello.cpp

This produces an output file called hello.s, containing the assembler code. You can look at it with your text editor – the code for the main function looks like this:

main:
	pushl	%ebp
	movl	%esp, %ebp
	andl	$-16, %esp
	subl	$32, %esp
	call	___main
	movl	$LC0, 4(%esp)
	movl	$__ZSt4cout, (%esp)
	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	28(%esp), %edx
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	__ZNSolsEi
	movl	$__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
	movl	%eax, (%esp)
	call	__ZNSolsEPFRSoS_E
	movl	$0, %eax
	leave
	ret

Lastly, and most usefully, you can get the compiler to output the object code file and not produce an executable. To see why this is useful, we need to consider how to use modular code in C++.

Remember:

  • Use the -E option to produce pre-processor output and the -S option to produce assembly language.

Modular C++

In theory, there is nothing to stop you writing all your C++ for an application in a single source file. However, in practice there area lot of problems with doing this:

  • It makes it difficult for several programmers to work on the same project
  • You have to re-compile all your code every time you make even the smallest change – this can be very slow.
  • You can’t easily share code between projects.
  • For a big project, you may go over the number of lines of code your compiler and/or editor can cope with.

And so on. Bearing all these issues in mind, C++ programmers typically split their projects up into a number of source and header files, compile them separately, and then link them to create the executable. You can do all this using the g++ driver, as we shall see.

To illustrate this, we’ll create a small C++ project which could form the basis for a game. We’ll write a function called Roll() that takes an integer parameter which specifies how many times to roll a six-sided dice, and returns the resulting rolls in a C++ vector. We’ll implement this in a file called roll.cpp:

#include <cstdlib>
#include <ctime>
#include <vector>
#include "roll.h"

std::vector <int> Roll( unsigned int n ) {
    std::srand( std::time( 0 ) );
    std::vector <int> rolls;
    while( n-- ) {
        rolls.push_back( 1 + rand() % 6 );
    }
    return rolls;
}

We’ll also provide a header called roll.h that declares the Roll() function:

#ifndef INC_ROLL_H
#define INC_ROLL_H
#include <vector>
std::vector <int> Roll( unsigned int n );
#endif

And a file containing the main function called rmain.cpp:

#include <iostream>
#include "roll.h"
using namespace std;

int main() {
    vector <int> result = Roll( 3 );
    for ( unsigned int i = 0; i < result.size(); i++ ) {	
        cout << result[i] << " ";
    }
    cout << endl;
}

The simplest way of compiling these files is simply to put the names of the sources on the g++ command line:

$ g++ rmain.cpp roll.cpp -o roll
$ roll
5 2 4

However, this is not much better than having all the code in the same file. We want to compile the files separately and then link them. To do that, we need to use the -c option of GCC:

$ g++ -c rmain.cpp -o rmain.o
$ g++ -c roll.cpp -o roll.o

The -c option says “do all the compilation steps up to the production of object code, but don’t link to produce an executable”. So these two commands produce two object files, rmain.o and roll.o, but no executable. To produce the executable, we use g++ again:

$ g++ rmain.o roll.o -o roll

The g++ driver is smart enough to recognise the object code files and know that it it doesn’t need to recompile them. From this point on, we need only compile the file(s) that actually get changed. For example, if we edited rmain.cpp to change the number of dice rolls from 3 to 4, we would only need to recompile rmain.cpp

$ g++ -c rmain.cpp -o rmain.o
$ g++ rmain.o roll.o -o roll

We could also put these two commands together:

$ g++ rmain.cpp roll.o -o rmain.o

Once again, g++ is smart enough to know it needs to recompile rmain.cpp, but that roll.o has already been compiled.

Managing exactly what needs to be compiled can get complicated – this is what make and similar tools are made to do. Using these tools is will have to be the subject of another tutorial.

Remember:

  • C++ programs should be created in a modular way.
  • Use the -c option to compile to object code but not link.

Using Libraries

The most common use of modular composition and separate compilation in C++ is the use of libraries. C++ is heavily dependant on libraries, so its important to know how to use them. We’ll look at creating a library, and then see how to link with it.

A static library for GCC is simply an archive of object code files, with an index to help speed up searches. To create something vaguely realistic, we’ll add another couple of files to the mix – strnum.cpp, which supplies two utility conversion functions, and the associated header, strnum.h. This is strnum.cpp:

#include <string>
#include <cstdlib>
#include "strnum.h"

long ToLong( const std::string & s ) {
    char * p;
    long result = std::strtol( s.c_str(), & p, 10 );
    if ( * p ) {
        throw "conversion to long failed";
    }
    return result;
}

double ToDouble( const std::string & s ) {
    char * p;
    double result = std::strtod( s.c_str(), & p );
    if ( * p ) {
        throw "conversion to double failed";
    }
    return result;
}

and this is strnum.h:

#ifndef INC_STRNUM_H
#define INC_STRNUM_H
#include <string>
long ToLong( const std::string & s );
double ToDouble( const std::string & s );
#endif

Note that throwing a const char * is done simply to save space – really you should throw an exception object.

We can now compile our new functions and the original Roll() function:

$ g++ -c strnum.cpp -o strnum.o
$ g++ -c roll.cpp -o roll.o

To turn these functions into a static library, we need to use the GCC “ar” tool. This takes the object files and slaps them together inside a single archive file:

$ ar rs libmystuff.a strnum.o roll.o

The “r” command says to replace files that already exist, and the “s” option says to build an index in the archive. The new library will be called “libmystuff.a” – C++ libraries typically have names beginning with “lib” and have the “.a” (for archive) extension.

We can now link with our new library – let’s modify rmain.cpp to use the new functions:

#include <iostream>
#include "roll.h"
#include "strnum.h"
using namespace std;

int main( int argc, char *argv[] ) {
    try {
        if ( argc != 2 ) {
            throw "usage: rmain count";
        }
        long n = ToLong( argv[1] );
        vector  result = Roll( n );
        for ( unsigned int i = 0; i < result.size(); i++ ) {
            cout << result[i] << " ";
        }
        cout << endl;
        return 0;
    }
    catch( const char * msg ) {
        cerr << msg << endl;
        return 1;
    }
}

Now we can build our application using the library:

$ g++ rmain.cpp libmystuff.a -o roll
$ roll 5
3 2 5 1 3

Note that once again, g++ is smart enough to know that libmystuff.a is a library, and to do the right thing, which is simply to link with it.

Remember:

  • Create object files created using the -c option of GCC.
  • Create the library using the “ar” tool.

Search Paths

Unfortunately, it is pretty rare for the library to be in the same directory as the application you are building. Mostly, libraries live at th end of a complicated directory path, and their header files at the end of a similarly complicated, but subtly different one. GCC has options for specifying these paths, which we’ll now look at. To make this more realistic, we are going to move our library and header files:

$ mkdir c:/mylib c:/mylib/lib c:/mylib/inc
$ mv libmystuff.a c:/mylib/lib
$ mv strnum.h roll.h c:/mylib/inc

If you are following this tutorial and using the Windows command prompt, the commands will be somewhat different. Now if we try to compile:

$ g++ rmain.cpp libmystuff.a

we will get errors saying the header files cannot be found. We’ll fix that first. The option to specify where to look for headers is -I:

$ g++ -Ic:/mylib/inc rmain.cpp libmystuff.a

Where in the command the -I appears is not important, but I like to place it before the file names. You can have as many -I options as you like, so if you were using multiple libraries you might have something like this:

g++ -Ic:/mylib/inc -Ic:/another/inc rmain.cpp libmystuff.a

This fixes the inclusion of header files, but GCC will still complain about not being able to find the library. You could simply say:

$ g++ -Ic:/mylib/inc rmain.cpp c:/mylib/lib/libmystuff.a

However, this gets a bit old when (as frequently happens) you are linking against a lot of libraries from the same directory. GCC has a “shorthand” for specifying the library path and the library name:

$ g++ -Ic:/mylib/inc  rmain.cpp -Lc:/mylib/lib -lmystuff

The -L option specifies a library search path (as with -I, you can have as many of these as you want) and the -l option the library name. Notice that we have omitted the “lib” prefix and the “.a” extension – these will be added for you by GCC if you use the -l option to name the library (once again, you can have as many -l options as you want).

One last wrinkle! If you write your GCC command like this (note I’ve switched the positions of rmain.cpp and the library):

$ g++ -Ic:/mylib/inc -Lc:/mylib/lib -lmystuff  rmain.cpp

you will be unpleasantly surprised to get several “undefined reference” errors. This one bamboozles even experience programmers, because they have forgotten a key thing about GCC – it only searches libraries if it actually has unresolved symbol names at the point in the command line the library name is mentioned. In the above command, rmain.cpp has not yet been compiled when the library name is encountered, so there are no unresolved symbols, and the library is not searched. A bit later, rmain.cpp is compiled, but our library is not searched, and so we end up with undefined references. The solution is simple – always make the libraries the last things on the command line.

Remember:

  • Specify include paths with the -I option
  • Specify library paths with the -L option
  • Specify the trimmed library name with the -l option
  • Order is significant! Put libraries last on the command line.

That concludes this part of the tutorial. In the final part, I’ll look at how to use the GCC command line to control debugging and optimisation options.

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: