Getting stared with C++

Depending on your background, you may need only to read certain parts of this lengthy page. It provides information on how to connect to the Math/CS Department server (graphically), getting started with XEmacs, structure C++ files (including basic use of classes and objects), compile and link C++ programs, and use a source-level debugger on a C++ program. Skip over any parts that you don't need!


Logging into sirius

For this course, I will assume that you will use sirius.cs.amherst.edu (the Math/CS Department server) to do your work. While you are welcome to do your work on another system (perhaps your own desktop/laptop machine), your code must compile, run, and be submitted on sirius. This page and others like it will assume that you are using sirius.

Note that if you are in SMudd 007, you can simply login to sirius directly using one of the NeoWare X-Terminal machines. You do not need SSH or VNC.

We begin by describing how to connect to sirius from outside of SMudd 007 using VNC, which will provide you a graphical interface. More specifically, this section will desribe how to establish a VNC connection that is tunneled through an SSH connection. The use of SSH ensures that your communication is encrypted, which is a good habit considering that the Amherst College network, as well as many other networks connected to the Internet, are open and rather insecure. If you are connecting from outside of the Amherst College network, you must use SSH tunneling to establish a VNC connection. I recommend that you use SSH tunneling under all circumstances.

The following steps will allow you to establish a VNC connection using SSH tunneling:

  1. Connect to sirius using an SSH client configured to establish the tunnel: The choice of SSH client and the description of how to use it depends on the type of system that you are using. Below are instructions for Mac OS X, Linux/UNIX, and Windows systems. If you have another type of system (such as Mac OS 9), contact me and I will attempt to point you a web page or a person that can help you.

  2. Open a VNC connection: Again, the software used in system dependant, and I describe how to use software for each of the systems described above:

  3. Login to sirius: Now that you are graphically connected, simply use the login window. A windowing environment will appear.

  4. Open a shell: There should be, along the bottom of your screen, a set of icons. Among these should be an icon that looks like a red hat in the lower-left corner. It works like a Windows ``start'' menu. Click that, and then select System Tools, and within that menu select Terminal. You should get a new terminal window in which a shell will start and you will be given a command line.

You can now issue commands or use the graphical interface to work on sirius. Note that there are an enormous number of programs available on this system, and a number of variations that you can choose for graphical interaction, including complete different windowing environments. Experiment with these at your own risk!


On Using XEmacs

I will assume that you are going to use XEmacs as your programming editor and environment. Feel free to use something else, but I will only likely be able to help you with this particular program (or, for those who perfer is, the original and similar Emacs). I recommend XEmacs because it will help to format C++ code, allow you to open a shell within it, direct your compilations (see below), and direct your debugging (see below).

I will also assume that you are at least modestly familiar with XEmacs from previous courses. If this program is completely new to you, try the following steps:

You may notice that in using XEmacs, the window may split, perhaps multiple times. (This window splitting occurs, for example, when you use XEmacs to direct compilation and debugging.) You can control this splitting with the commands given below. (Try them!)

Some of the sections below will describe more advanced uses of XEmacs that are you likely to find helpful in working on these projects.


C++ File Layout

C++ is a modular language in that you can write independent modules of code that can be separately compiled into an object file, and later the object files can be linked together to form a single executable file. Java is somewhat similar in that each .java file is its module code. The Java compiler (javac) will compile each code such file into an object file (.class). When you run a Java program using java, one of the things is does (more or less) is link those object files to form a single, running program.

First, observe that the commands for compiling, linking, and running C++ programs is different from Java. Second, note that each C++ module comprises two files instead of one. This section will describe the basics of creating such a module, and the next section will describe how to compile it and link it with others.

We'll begin with a very simple, single-module program. In this simple case, we will need only one file for the module, and we will be able to compile and link it in one step. Consider single file, named sample-program.cc, with the following contents:

#include <stdio.h>

int double_it (int x) {

  return (x * 2);

}

int main () {

  int i = 15;
  int j = double_it(x);

  printf("i = %d, j = %d\n", i, j);

  return 0;

}
    

There are a few important things to notice about this simple program:

You can compile this single C++ module directly into an executable form, using the following command line:

g++ -o sample-program sample-program.cc

The result will be an executable file named sample-program. To run this file, you simply enter the filename as a command to the shell, like so:

./sample-program

Notice that you must preceed the filename with a ./ to ensure that the program will run. The dot (.) indicates to the shell that you're interested in something within the current directory, and the slash (/) separates the current-directory indicator from the filename itself. Without this prefix, the shell may not know that it should look for executable programs within the current directory, and will thus complain that it cannot find the command named sample-program.

Now we can move on to seeing how to create a C++ program using multiple modules. Our example will include the use of object orientation, since it is likely that you will want to create classes and objects for later projects.

Consider first a module that contains only the main() function for this program. Since it only contains that function, it comprises only a single file named main.cc whose contents are as follows:

#include <stdio.h>
#include "Foo.hh"

int main () {

  // Create a new Foo object on the heap.
  Foo* x = new Foo(5);

  // Get the Foo object to do its thing.
  x->do_something();

  // Eliminate the object.
  delete x;
  x = NULL;

  return 0;

}
    

Notice that there is a second #include directive for this module. Because it will make use of the Foo class which will be contained in another module, this one must import that module to compile successfully. Because the module being used is not one of the standard modules, we place the filename in quotes ("") to indicate that the compiler should search the current directory to find that file.

Now we create a module for the Foo class. Note that it is good programming practice (but not strictly necessary) to write one module for each class that you create. Also note that in C++, a module doesn't need to contain a class, as it may contain any collection of data and functions (thus not using the object oriented features of the language). We will see some non-OO use of C++ in later projects.

Any module that is going to be imported by another module (as is the case with the Foo module here) must be written as two files. These files are:

  1. The header file (using the .hh suffix) will define classes and declare functions and variables that may be used by other modules. In short, it is the external interface that should be presented to other modules, revealing only the elements necessary to use that module.

  2. The code file (using the .cc suffix) will contain the definitions of functions and methods and the assignments of variables. In other words, it is the internal, inner workings of a module, usually consisting of the actual function and method definitions.

First, we present the header file for the Foo class module which would be contained in the file Foo.hh, and whose contents would look like this:

#if !defined (_FOO_HH)
#define _FOO_HH

class Foo {

public:

  // The constructor.
  Foo (int x);

  // A method that does, well, _something_...
  void do_something ();

private:

  int value;

};

#endif // _FOO_HH
    

There are a number of elements of this file that bear explanation, and we provide those below. First, notice that this is the file that the main module will import. The #include directive that mentions this file will simply cause the compiler to read and process this entire file as though it were copied-and-pasted into the location of the #include line. Now, onto the observations about the file itself:

Now onto the code file for the Foo class module, which would be named Foo.cc and would look like this:

#include <stdio.h>
#include "Foo.hh"

// The constructor.
Foo::Foo (int x) {

  value = x;

}

void Foo::do_something () {

  printf("The value, in hex, happens to be %x\n", value);

}
    

Again, some observations about this file:

Given this collection of files, we'll move onto the next section, where we will consider performing a more complex compilation and linking of these modules.


Compiling and linking

How can we compile and link multiple C++ modules into an executable file? We will examine the steps required in this section, showing not only how to compile and link, but also how to do so within XEmacs, which can help in the task of examining compilation errors.

Throughout this section, we will continue the examine begun in the previous section. This example assumes two modules. The first is the main module which contains only the main() function, and consists only of a code file named main.cc. The second is the Foo module which contains all components of the Foo class, and exists as a header file name Foo.hh and a code file name Foo.cc.

Note that this section does not address the use of the make utility. We will encounter make in project 1.

Follow these steps to compile and link this (silly little) program:

  1. Compile the main module: Within XEmacs, use the following command:

    M-x compile

    XEmacs will prompt you to enter a specific command to perform compilation. By default, it will provide make -k as this command. Delete that default, and instead provide the following command:

    g++ -ggdb -c main.cc

    You will see XEmacs split the window and open another buffer to hold the results of the compilation. The actual compilation should only take a few moments.

    The first option passed to the compiler (-ggdb) tells it to include extra debugging information in its output. The second option (-c) tells the compiler only to perform the compilation step, and not the linking step. Therefore, the result of this command will be a file named main.o, which is an object file.

  2. Introduce an error into the Foo module: For instructional purposes, try modifying Foo.cc so that it contains an error. For example, change the use of one of the variables so that it is misspelled. You will therefore be able to see how XEmacs can help handle compilation errors.

  3. Start another compilation in XEmacs using the same command as before. Notice that it will remember your last compilation command and present that as a default. Edit the default so that you issue the following command to compile the Foo module:

    g++ -ggdb -c Foo.cc

    Notice that you need only tell the compiler about the code file of the module. The header file will be used whenever a #include directive requests it, as this module and the main module do.

  4. Use XEmacs to find the compilation error: The compilation started in the previous step will indicate an error due to the modification to Foo.cc performed earlier. Try the following command to XEmacs (and don't forget the backquote (`)):

    C-x `

    XEmacs will show, in the compilation buffer, the error. Meanwhile, it will also move the cursor in the Foo.cc buffer to the line that contained the error. Repeated use of the above command will step through each compilation error in sequence (if you have multiple such errors), where XEmacs will do the work of finding the location of the error in your code.

  5. Fix the error and recompile: Simply fix the error that you introduced into Foo.cc and then compile the Foo module again. An object file named Foo.o should be produced.

  6. Link the object files: We want to join the two object files to form a single, executable file. Use the M-x compile command within XEmacs again. Alter the compilation command to be:

    g++ -o final-product main.o Foo.o

    This command will produce an executable file named final-product. It contains not only the object code from main.i and Foo.o, but also some special bootstrapping code that allows the program to begin running and to call main(). The places where main() uses Foo objects and methods have been linked together.

  7. Run the program: If you haven't already, open a shell within XEmacs itself. Specifically, use the command:

    M-x shell

    At the command line, run your program by entering:

    ./final-product

    It should executing, doing something not at all particularly interesting, and then exiting. If that happens, you've successfully written, compiled, and linked a multi-module C++ program.


Debugging

Although the sample programs that we've presented on this page are simple and should work, it is likely that you will write code that will not immediately run properly. Using GDB, a source-level debugger, will allow you to control the execution of your programs and to catch errors. XEmacs can control the execution of GDB, particularly by showing you the location in your code of the current point of execution. We'll see here how to get GDB started within XEmacs, and how to perform some basic tasks with the debugger.

We'll use the final-product program generated by the multi-module example above. You could use some other program, but it will work well only if the program was compiled using the -ggdb option. Follow these steps:

  1. Starting GDB: You can start the debugger on your executable by using the XEmacs command:

    M-x gdb

    The editor will ask you to select a file on which you want to run GDB, providing the current directory as the starting point for a complete pathname for that file. Type the name of the desired executable, which in this case is final-product).

    The window will split, and a new buffer will open up, with GDB running and your executable having been loaded into it. You will be given a prompt that reads:

    (gdb)
  2. Be aware of the help: GDB contains its own, online help section. Simply use the command:

    (gdb) help

    This help facility provides plenty of information. However, you may prefer to view the HTML documentation for GDB.

  3. Run your program: You can simply run your program within GDB like so:

    (gdb) run

    First, note that if your program has no errors, then it will run normally to completion, leaving you back at the (gdb) prompt. If there were errors, execution would stop, and GDB would show you the point of execution at which the error occurred.

    Second, if your program requires command-line arguments, you should enter them after the run command just as you would if running the program outside of GDB and at a shell prompt.

  4. Set a breakpoint: Since the example we're assuming contains no errors, execution will run to completion in the previous step. To see how GDB can allow you to control the execution of a program, you can set a breakpoint, which is a location in the code where the debugger should pause execution and wait for further commands. Let's set a breakpoint in a Foo method, like so:

    (gdb) b Foo::do_something

    This command will stop execution any time the do_something() method is called on a Foo object. Note that you must explicitly scope method names within GDB. If you were to specify a function instead of a method, you would provide only the function name.

    You can also set a breakpoint at an arbitrary line of code, like so:

    (gdb) b main.cc:13

    This command will stop execution at line 13 of main.cc, which is where the Foo object is deleted within main(). If you specify a line number that does not contain a C++ expression (for example, a line containing only a comment), then GDB will set a breakpoint at the nearest following line that does contain an expression.

  5. Re-run your program: Run the program again with the run command, and you will execution stop at the Foo::do_something() breakpoint. Notice that GDB will open the Foo.cc file and point to the next line to be executed. The debugger will provide its prompt and wait for a command. The following steps will describe some of the many things you can do once execution has been paused.

  6. Examining the activation stack: There are many things you can do to examine the state of the program. A good starting point is obtaining a backtrace:

    (gdb) bt

    This command shows you the activation stack; that is, which functions called which others, and with what values for the arguments. You will see, in our example, that main() has called Foo::do_something().

  7. Viewing variables values: You can print the values of any variables visible within the current scope. For example, within do_something, the data member value is visible, and you can see its contents with this command:

    (gdb) p value

    It is sometimes useful to view a value in hexidecimal, which you can do like so:

    (gdb) p/x value

    This command can also be used to print more complicated values. If you had a variable named my_array that pointed to an array of pointers to Foo objects, then you could examine the value data member of an object at which the 3rd element of the array points:

    p my_array[3]->value
  8. Moving through the stack: You may wish to examine the variables from the other frames shown in the activation stack. You can select the frame number (notice that the bt command enumerates the frames) that you would like to see and operate within, like so:

    f 1

    In our examine, you will be brought into the frame for main(), and shown the point at which that function called Foo::do_something(). The variables local to main() become visible as part of the current scope, and you can examine their contents. For example, you can examine the pointer to the Foo object:

    (gdb) p/x x
  9. Step-by-step execution: Now that the program is paused, you can continue the execution one step at a time. First, you can step line-by-line with the next command, like so:

    (gdb) n

    This command will execute one line of code and return you to the prompt. If there is a function call on that line of code, the next command will step over that call; that is, the whole function call will be performed, and you will regain control at the next line of code, where that function call will have returned.

    In contrast, you can use the step command to step into such function calls, like so:

    (gdb) s

    This command also progesses one line of code at a time. However, if a line of code contains a function/method call, it will jump into that function/method, stopping at the first line that contains an expression. An examination of the stack will show you that a new frame has been pushed.

  10. Continuing execution: When you no longer want to control execution step-by-step, you can tell GDB to resume full-speed execution with the continue command:

    (gdb) c

    This command will simply resume execution until an error, a breakpoint, or the end of the program is reached.

Most of all, play with GDB, as it can do a number of useful things to help you find errors. In programs that do not execute correctly, the challenge will be to figure out how to stop program execution with a breakpoint just slightly before the error occurs. That way, you can see the error occur as you control the execution, keeping track of the way in which the variable values change, and thus discovering the point at which the computation veers off track.


Scott F. Kaplan
Last modified: Tue Sep 27 09:40:27 EDT 2005