Computational Geometry (Fall 2004)

Hints for use of C++ and other development tools at DAIMI

Contents

The C++ compiler

Below I describe some simple uses of the GNU C++ compiler (g++). Other C++ compilers, e.g., CC on the SGIs will not accept all of the same command line options, but will typically have other options with equivalent behavior. Consult the man pages for the compiler to learn about its possibilities. For instance: the -frepo option for g++ is default behavior of the SGI CC compiler, and the g++ -Wall option means almost the same as the -fullwarn option to the SGI compiler.

One of the advantages of the g++ compiler is that it exists on all the DAIMI Unix machines and behaves the same on all the platforms. Much documentation about g++ exists in the Emacs info system (choose "browse manuals" in the Emacs help menu).

Simple compilation of a C++ program

	g++ -Wall -g program.cpp -o program

This compiles the C++ program in program.cpp into executable code in the file program. The command line options means the following:

-Wall:
Read -W all, i.e., ask g++ to generate lots of warnings about coding style etc. This helps to find bug, eg. using = where == is meant, use of uninitialized variables and much more. Reading and getting rid of these warnings can save hours of debugging later on.
-O:
Optimize. Generates better code, also makes more warnings possible. More optimization can be turned on by -O2.

On the other hand, -O can confuse gdb during debugging...

-g:
Generate debugging information. This is necessary for later debugging the program with gdb, ddd, xdb, xdbx or other debuggers.
-frepo:
Generate template code repository (.rpo) files. This can be an advantage in larger projects with many .cpp files. The -frepo option keeps track of where template code is instantiated, such that a specific instance is present only once in the final program.
-o program:
Place the compiled program in the file "program" instead of in "a.out" which is the default.

Remember

When you want to execute the above program from the command line, use the command
	./program
and not just:
	program

Many beginning Unix programmers have burnt their fingers by typing

	test

and finding that this didn't work as they expected, as this command usually executes the program /bin/test and not the program in the current directory.

Conditional compilation

When using conditional compilation and the C preprocessor it is useful to know about the -D option of the C and C++ compiler. For example:
	g++ ... -DFOO ... pgm.cpp
The macro FOO is now defined during the compilation of pgm.cpp. It can be tested using #ifdef FOO ... #endif. The use may also be a little more advanced:
	g++ -DDEBUGLEVEL=7 ...
used together with:
#if DEBUGLEVEL > 4
 ....
#endif
Macro names are traditionally written in ALLCAPS.

When debugging it may be a good idea to have a run-time variable deciding how much debug info the program should print out. One example could be:

int
func(double x)
{
#ifdef DEBUG
	if(debuglevel > 0) {
		printf("entered func\n");
	}
	if(debuglevel > 3) {
		printf("x = %f\n", x);
	}
#endif
	...
}
Normally one would compile the program with -DDEBUG until completely sure that the program is bug-free.

Getopt

Most programs under Unix need to parse command line options of the type -f, -x argument. There is no need to re-invent the wheel here. The standard C library provides the function getopt() to parse this kind of command line options. See man 3 getopt. An example:
#include <getopt.h>

int main(int argc, char *argv[]) {
        int ch;
        ...
        while ((ch = getopt(argc, argv, "x:f"))) {
                switch (ch) {
                case 'x':
                        /* option -x was used */
                        printf("argument = %s\n", optarg);
                        break;
                case 'f':
                        /* option -f */
                        break;
                default:
                        /* error */
                        break;
                }
        }
}

Debugging

When bugs turn up in ones program, it is a good idea to use a debugger (typically gdb) to locate the bug. One easy way to use gdb is from within Emacs. (Another is to use xxgdb, xdbx or ddd if available). From within Emacs you start gdb like this:
	M-x gdb

Emacs then asks for the name of the program that you want to debug. It should have been compiled with the -g option as described above. Emacs opens a buffer called *gud-...* (for Grand Unified Debugger). You may now set break-points in the program and run it under gdb. See how to use the gdb from within Emacs in the Help menu (Describe mode).

Here is a small sample of common gdb commands:

C-x space
on a line in the source code sets a break point.
r [arguments]
Starts the program with the given arguments.
p <variable>
Prints the value of a program variable.
s
Single step.
n
Next instruction.
bt
Prints the call-stack (back trace).
q
Quit.

Gdb can do much more, and is described in the Emacs info system (C-h i) or choose "browse manuals" from the Help menu.

Assertions

Assertions can be a very useful thing when debugging your software. They mostly help to check program invariants and find errors earlier that would otherwise be possible.

In the beginning of your code you include assert.h:

#include <assert.h>
For instance, to check that the function foo() is always called with a positive argument, you may write:

int
foo(int n)
{
	assert(n > 0);

	... bla bla ...
}

At run-time the assert() macro checks that n is positive, and if not the program aborts with an error message and a core dump that can be used to find the error:

Assertion failed: n > 0 at line 545 in "program.c"

It is good practice to have assertions checking the pre-conditions of all parameters to all functions in your program. Real consistency-check routines may also come in handy if you have complicated data structures with invariants that need to be maintained!

It is important not to change the state of you program within the assert macros. The assertions can be switched off at compile time (with -DNDEBUG) when you are sure that the program works, and the assertions will then not be included in the program. Statements like:

  assert(n = 0);
are therefore a big no-no!

Make

Make is a valuable tool when you develop programs consisting of more than one or two files. Makefiles are used to collect the commands used to compile and link a larger program complex, and to ensure that only the files that have been changed are re-compiled. This is a huge advantage when ones programs gets larger than maybe 1000 lines.

Makefiles specify dependencies and how to get from source files to the finished executable.

Here is a simple Makefile. You may copy it from this template.

# Comments are nice
# Here a macro definition specifying the source files
SRC	=	modula.cpp modulb.cpp main.cpp
# another macro, as SRC, but replace .cpp by .o
OBJ	=	$(SRC:.cpp=.o)
CC	=	g++
CFLAGS	=	-Wall -O -g

# Here is the rule of how to get from a .cpp file to a .o file:
# The $< macro expands into a source file name
.cpp.o:
	$(CC) $(CFLAGS) -c $<

# a dependency: to make the target "all", first do the target "pgm"
all:	pgm

# another dependency: to make the target pgm, make the files that OBJ
# expands to, here "modula.o modulb.o main.o", then link the object
# files with the $(CC) command into the executable pgm
pgm:	$(OBJ)
	$(CC) $(OBJ) -o pgm

# How does make find out how the $(OBJ) object files are made? It has
# default rules for this, eg. the .cpp.o rule above.

Note

Commands in Makefiles start with a TAB character, not spaces, but ASCII 9. That is, the first character in the line $(CC) $(OBJ) -o pgm above is a TAB character. If you paste from an xterm into emacs, then TABs will be converted into spaces and the make commands will not work. You may recognize a TAB character in emacs when the cursor jumps from the start of the line to the first $ character in one jump when you use the cursor keys.

You will often need more advanced dependencies among files. For example if modulea.h contains definitions that are used (#included) in both modulea.cpp and moduleb.cpp; when modulea.h changes then both modulea.cpp and moduleb.cpp need to be recompiled. You may use gcc to find these dependencies:

At the end of the above Makefile you append the following line:

include dependencies
No # or quotes here...

You now execute the following command whenever you change includes in you programs:

	gcc -MM *.cpp > dependencies
Gcc then computes which .cpp files include which .h files, and writes the dependencies in to the dependencies file, which is included in the Makefile by make.

To re-compile your entire program complex you now just write:

	make
and make knows which modules to re-compile.

Make from within Emacs

If you use Emacs it may be useful to start make from within emacs so that you can use emacs to go to the next error line and so on. The command to use is:
	M-x compile

Manuals

RTFM (Read The F*ing Manual). All standard C library routines (libc) documented on-line:
	man 3 <name of function>
looks up in the third section of the on-line manual.
	man -k <keyword>
looks in the table of contents for <keyword>, for instance "man -k memory".

GCC, gdb, and many other tools are described in the Emacs info system. Choose "browse manuals" from the help menu or enter info-mode using "C-h i".

Include files

Standard include files (header files) referred to in C++ as for example
#include <stdio.h>
are to be found under /usr/include/, /usr/include/CC/ and /usr/include/g++-include/. Reading these may be useful at times. Your own include files are used as:
#include "foobar.h"
Note the use of quotes instead of angle brackets. The C++ compiler will look for these files in the current directory or in directories specified at the command line as -I<directory>.

A couple of things that the fresh C++ programmer should know: You do NOT use include files to divide your code into smaller parts. You do NOT include .cpp or .c files. You include .h files with common type definitions, constants and prototypes in a number of .cpp files that are then compiled separately and linked together at last. See below.

The compilation process

In general a C++ consists of a number of modules (.cpp files) that are compiled separately. This is coordinated by a Makefile as described above.

The figure below illustrates how a program consisting of two modules p1.cpp and p2.cpp are compiled and linked into a executable program a.out.

p1.cpp ---> CPP ---> CC1plus ---> AS ---> p1.o
  			                     \__ LD ---> a.out
			                     /
p2.cpp ---> CPP ---> CC1plus ---> AS ---> p2.o
Every module is separately sent through CPP (the C preprocessor, taking care of all the #define and #include directives and macro expansion). The output of CPP is one large C++ file (you may see this expansion using "g++ -e file.cpp").

CC1plus is the real C++ compiler which translates from C++ to assembly. AS is the assembler compiling symbolic assembly into binary code in the .o object file.

The linker LD takes the object files which may refer to each other to various libraries and links them together into the final executable. Nowadays the libraries are seldom linked into the executable, only a stub is linked in and the real library is dynamically linked in when the program is started.

G++ (and CC) is a front end to all of this process. G++ knows, for example, that it need only run the linker on a set of .o files. The -c option to G++ tells it to stop before the link phase.

Standard Template Library

The Standard Template Library (called STL) is a standardized C++ template library that implements a lot of common data structures: dynamically sized strings, singly- and doubly-linked lists, vectors, sets, hash tables, maps etc. It is a good idea to learn to use the STL because it can save a lot of programming time.

Here is a small example of using a STL map which is implemented by a red-black tree:

#include <string>
#include <map>

using namespace std; // the STL types are in the std namespace

typedef map<string, int> StringMap;
StringMap months;

months[string("January")] = 31;
months[string("February")] = 28;
months[string("March")] = 31;
months[string("April")] = 30;
...

Special C++ considerations

C++ contains a lot of powerful features: operator overloading, method overloading, default parameter values, special memory allocator operators, templates, exception handling, reference types and much more. Here are some notes about my personal opinion about when to use them:

CVS

It can be advantageous to use CVS to keep track of the source code in a project where several persons work together. The idea of CVS is to have a source code repository where all the old versions of the source code are kept. When person A wants to edit file.cpp she executes the command:
	cvs update file.c
which means that the most recent commited changes are merged into her local copy of file.c. Others in the group can edit their local copy of file.c simultaneously. When she is finished editing (and can compile the file) she says:
	cvs commit file.c
which will commit her changes into the respository. Commit starts an editor where A can write a message about what was changed. If others have commited changes to file.c in the meantime she will have to do a
	cvs update
to merge their changes into her local copy of file.c first.

The only commands frequently used are "cvs update" and "cvs commit". "cvs diff" may also be useful to see the changes made between different versions.

CVS means Concurrent Version System, and builds on top of RCS (Revision Control System).

How to get started?

A group member is selected to keep the repository. He then makes a new empty repository like this:
	cvs -d /users/XXXX/project/repository init
This results in the creation of a /users/XXXX/project/repository/CVSROOT directory.

In their .login file each group member should have a line like:

	setenv CVSROOT /users/XXXX/project/repositotry
			      ^^^ replace by suitable user name
To put existing files from an examples/ directory into the CVS repository you must first remember to clear out all binaries and temporary files from the examples/ directory before importing the sources into CVS. Then do:
	cd .../examples/		<--- for instance
	cvs import ex1 foo bar
Ex1 is the name of the CVS module, that is, it refers to the bunch of files under examples. Foo and bar are vendor and branch tags. This will create a repository/ex1/ directory which will contain the revisions of the example program complex.

You may now check out the module from CVS by:

	cd /somewhere/else
	cvs checkout ex1
This will create a directory ex1/ where local copies of the source files will exist.

In order for this to work for all members of a group, the keeper of the repository should give permission to read and write the repository to the group members.

New files are added to the repository by

	cvs add fil2.c
See also "man cvs" or read the info-pages in Emacs.

Peter Ørbæk, poe@daimi.au.dk, August 2001
Modified by Gerth Stølting Brodal, gerth@cs.au.dk, September 2004