1.1. Compiling and Executing Programs

Compiling C and C++

Now that you know how to create and edit files, you can generate new programs. The most commonly used languages in the CS Department at the moment are C++, C, and Java.

The most popular C++ and C compilers are g++ and gcc.[35]

The Structure of C++ and C Programs

Although not really a Unix-specific topic, it's hard to discuss how to compile code under any operating system without a basic understanding how programs are put together.

The source code for a C++ (or C) program is contained in a number of text files called source files. Very simple programs might be contained within a single source file, but as our programs grow larger and more complicated, programmers try to keep things manageable by splitting the code into multiple source files, no one of which should be terribly long.

There are two different kinds of source files: header files and non-header files. Header files are generally given names ending in .h . Non-header files are generally given names ending in .cpp for C++ code and .c for C code.[36]

Header and non-header files are treated differently when we build programs. Each non-header file is compiled separately from the others (Figure 7.1, “Building 1 program from many files”). This helps keep the compilation times reasonable, particularly when we are fixing bugs in a program and may have changed only one or two non-header files. Only those changed files need to be recompiled.

Header files are not compiled directly, Instead, header files are included into other source files via #include. In fact, when you invoke a C/C++ compiler, before the real compiler starts, it runs a pre-processor whose job is to handle the special instructions that begin with #. In the case of #include statements, the pre-processor simply grabs the relevant header file and sticks its content into the program right at the spot of the #include.

Figure 7.1. Building 1 program from many files

Building 1 program from many files

This can result in a dramatic increase in the amount of code that actually gets processed. The code shown here, for example, is pretty basic. But the #include statements bring in an entire library of I/O and string-related declarations from the C++ standard library. Here, for example, is the output of the pre-processor for one compiler. (If you look at the very end, you can recognize the main code for this program.)

A header file can be #included from any number of other header and non-header files. That is, in fact, the whole point of having header files. Header files should contain declarations of things that need to be shared by multiple other source files. Non-header files should declare only things that do not need to be shared.

As we go through all the compilation steps required to build a program, anything that appears in a non-header file will be processed exactly once by the compiler. Anything that appears in a header file may be processed multiple times by the compiler.

What Goes Into a Header File? What Goes Into a Non-Header File?

The short answer is that a header file contains shared declarations, a non-header file contains definitions and local (non-shared) declarations.

What is the difference between a declaration and a definition?

Pretty much everything that has a name in C++ must be declared before you can use it. Many of these things must also be defined, but that can generally be done at a much later time.

You declare a name by saying what kind of thing it is:

  1  const int MaxSize;          // declares a constant
     extern int v;               // declares a variable
     void foo (int formalParam); // declares a function (and a formal parameter)
     class Bar{...};               // declares a class
  5  typedef Bar* BarPointer;    // declares a type name
    

In most cases, once you have declared a name, you can write code that uses it. Furthermore, a program may declare the same thing any number of times, as long as it does so consistently. That's why a single header file can be included by several different non-header files that make up a program - header files contain only declarations.

You define constants, variables, and functions as follows:

  1  const int MaxSize = 1000;                   // defines a constant
  2  int v;                                      // defines a variable
  3  void foo (int formalParam) {++formalParam;} // defines a function
  4 

A definition must be seen by the compiler once and only once in all the compilations that get linked together to form the final program. A definition is itself also a declaration (i.e., if you define something that hasn't been declared yet, that's OK. The definition will serve double duty as declaration and definition.).

When a non-header file is compiled, we get an object-code file, usually ending in .o . These are binary files that are almost executable - for some variables and functions, instead of the actual address of that variable/function, they still have its name. This happens when the variable or function is declared but not defined in that non-header file (after expansion of #includes by the pre-processor). That name will be assigned an address only when a file containing a definition of that name is compiled. And that address will only be recorded in the object code file corresponding to the non-header source file where the name was defined.

The complete executable program is then produced by linking all the object code files together. The job of the linker is to find, for each name appearing in the object code, the address that was eventually assigned to that name, make the substitution, and produce a true binary executable in which all names have been replaced by addresses.

Understanding this difference and how the entire compilation/build process works (Figure 7.1, “Building 1 program from many files”) can help to explain some common but confusingly similar error messages:

  • If the compiler says that a function is undeclared, it means that you tried to use it before presenting its declaration, or forgot to declare it at all.

  • The compiler never complains about definitions, because an apparently missing definition might just be in some other non-header file you are going to compile later. But when you try to produce the executable program by linking all the compiled object code files produced by the compiler, the linker may complain that a symbol is

    • undefined (none of the compiled files provided a definition) or is

    • multiply defined (you provided two definitions for one name, or somehow compiled the same definition into more than one object-code file).

    For example, if you forget a function body, the linker will eventually complain that the function is undefined. If you put a variable or function definition in a .h file and include that file from more than one place, the linker will complain that the name is multiply defined.

Compiling a Program With Only One Non-Header File

The simplest case for each compiler involves compiling a single-file program (or a program in which all files are combined by #include statements).

Example 7.1. Try This

  1. Use an editor (e.g., emacs) to prepare the following files:

         hello.cpp

    #include <iostream> 
    
    using namespace std;
    
    int main () 
    { 
      cout << "Hello from C++ !" << endl; 
      return 0; 
    }
    

         hello.c

    #include <stdio.h> 
    int main () 
    {
      printf ("Hello from C!\n"); 
      return 0;
    }
    

  2. To compile and run these, give the commands:

    g++ -g hello.cpp
    ls
    

    Notice that a file a.out has been created.

    ./a.out
    gcc -g hello.c
    ./a.out
    

    The compiler generates an executable program called a.out. If you don't like that name, you can use the mv command to rename it.

  3. Alternatively, use a -o option to specify the name you would like for the compiled program:

    g++ -g -o hello1 hello.cpp
    ./hello1
    gcc -g -o hello2 hello.c
    ./hello2
    


In the example above, we placed "./" in front of the file name of our compiled program to run it. In general, running programs is no different from running ordinary Unix commands. You just type


pathToProgramOrCommand parameters

In fact, almost all of the "commands" that we have used in this course are actually programs that were compiled as part of the installation of the Unix operating system.

As we have noted earlier, we don't usually give the command/program name as a lengthy file path. We say, for example, "ls" instead of "/bin/ls". That works because certain directories, such as /bin, are automatically searched for a program of the appropriate name. This set of directories is referred to as your execution path. Your account was set up so that the directories holding the most commonly used Unix commands and programs are already in the execution path.[37] You can see your path by giving the command

echo $PATH

One thing that you will likely find is that your $PATH probably does not include ".", your current directory. Placing the current directory into the $PATH is considered a (minor) security risk, but that means that, if we had simply typed "a.out" or "hello", those programs would not have been found because the current directory is not in the search path. Hence, we gave the explicit path to the program files, "./a.out" and "./hello".

Compiling With Multiple Non-Header Files

A typical program will consist of many .cpp files. (See Figure 7.1, “Building 1 program from many files”) Usually, each class or group of utility functions will have their definitions in a separate .cpp file that defines everything declared in the corresponding .h file. The .h file can then be #included by many different parts of the program that use those classes or functions, and the .cpp file can be separately compiled once, then the resulting object code file is linked together with the object code from other .cpp files to form the complete program.

Splitting the program into pieces like this helps, among other things, divide the responsibility for who can change what and reduces the amount of compilation that must take place after a change to a function body.

When you have a program consisting of multiple files to be compiled separately, add a -c option to each compilation. This will cause the compiler to generate a .o object code file instead of an executable. Then invoke the compiler on all the .o files together without the -c to link them together and produce an executable:

   g++ -g -c file1.cpp
   g++ -g -c file2.cpp
   g++ -g -c file3.cpp
   g++ -g -o programName file1.o file2.o file3.o

(If there are no other .o files in that directory, the last command can often be abbreviated to g++ -o programName -g *.o .) The same procedure works for the gcc compiler as well.

Actually, you don't have to type separate compilation commands for each file. You can do the whole thing in one step:

   g++ -g -o programName file1.cpp file2.cpp file3.cpp

But the step-by-step procedure is a good habit to get into. As you begin debugging your code, you are likely to make changes to only one file at a time. If, for example, you find and fix a bug in file2.cpp, you need to only recompile that file and relink:

   g++ -g -c file2.cpp
   g++ -g -o programName file1.o file2.o file3.o

Example 7.2. Try This

  1. Use an editor (e.g., emacs) to prepare the following files:

         hellomain.cpp

    #include <iostream> 
    #include "sayhello.h" 
    
    using namespace std;
    
    int main () 
    { 
      sayHello();
      return 0; 
    }
    

         sayhello.h

    #ifndef SAYHELLO_H
    #define SAYHELLO_H
    
       void sayHello();
    
    #endif
    

         sayhello.cpp

    #include <iostream>; 
    #include "sayhello.h" 
    
    using namespace std;
    
    void sayHello()
    {
      cout << "Hello in 2 parts!" << endl;
    }
    
    

  2. To compile and run these, give the commands:

       g++ -g -c sayhello.cpp
       g++ -g -c hellomain.cpp
       ls
       g++ -g -o hello1 sayhello.o hellomain.o
       ls
       ./hello1
    

    Note, when you do the first ls, that the first two g++ invocations created some .o files.

  3. Alternatively, you can compile these in one step. Give the command

    rm hello1 *.o
    ls
    

    just to clean up after the previous steps, then try compiling this way:

    g++ -g -o hello2 hellomain.cpp sayhello.cpp
    ls
    ./hello2
    
    


An even better way to manage multiple source files is to use the make command.

Some Useful Compiler Options

Another useful option in these compilers is -D. If you add an option -Dname=value , then all occurrences of the identifier name in the program will be replaced by value. This can be useful as a way of customizing programs without editing them. If you use this option without a value, -Dname , then the compiler still notes that name has been defined. This is useful in conjunction with compiler directive #ifdef, which causes certain code to be compiled only if a particular name is defined. For example, many programmers will insert debugging output into their code this way:

⋮
x = f(x, y, z);
#ifdef DEBUG
   cerr << "the value of X is: " << x << endl;
#endif
y = g(z,x);
⋮

The output statement in this code will be ignored by the compiler unless the option -DDEBUG is included in the command line when the compiler is run.[38]

Sometimes your program may need functions from a previously-compiled library. For example, the sqrt and other mathematical functions are kept in the m library (the filename is actually libm.a). To add functions from this library to your program, you would use the -lm option. (The m in -lm is the library name.) This is a linkage option, so it goes at the end of the command:

   g++ -g -c file1.cpp
   g++ -g -c file2.cpp
   g++ -g -c file3.cpp
   g++ -g -o programName file1.o file2.o file3.o -lm

The general form of gcc/g++ commands is

g++ compilation-option files linker-options

Here is a summary of the most commonly used options[39] for gcc/g++:

Compilation Flags
-c compile only, do not link
-o filename Use filename as the name of the compiled program
-Dsymbol=value Define symbol during compilation.
-g Include debugging information in compiled code (required if you want to be able to run the gdb debugger).
-O Optimize the compiled code (produces smaller, faster programs but takes longer to compile)
-I directory Add directory to the list of places searched when a ``system'' include (#include …) is encountered.
Linkage Flags
-L directory Add directory to the list of places searched for pre-compiled libraries.
-llibname Link with the precompiled library liblibname.a

Error Messages

Unfortunately, once you start writing your own code, you will almost certainly make some mistakes and get some error messages from the compiler.

This is likely to lead to two problems: reading the messages, and understanding the messages.

Capturing the Error Messages

Unless you are a far better programmer than I, you will not only get error messages, you will get so many that they overflow your telnet/xterm window.

How you handle this problem depends upon what command shell you are running, but there are two general approaches. You can use redirection and pipes to send the error messages somewhere more convenient, or you can use programs that try to capture all output from a running program (i.e., the compiler).

We've talked before about how many Unix commands are filters, working from a single input stream and producing a single output stream. Actually, there are 3 standard streams in most operating systems: standard input, standard output, and standard error. These generally default to the keyboard for standard input and the screen for the other two, unless either the program or the person running the program redirects one or more of these streams to a file or pipes the stream to/from another program.

  1. Pipes and redirection

    We introduced pipes and redirection earlier. The complicating factor here is that what you want to pipe or redirect is not the standard output stream, but the standard error stream. So, for example, doing something like

      g++ myprogram.cpp > compilation.log
    

    or

      g++ myprogram.cpp | more
    

    won't work, because these commands are only redirecting the standard output stream. The error messages will continue to blow on by.

    How you pipe or redirect the standard error stream depends on the shell you are running:

    Unix, running C-shell or TC-shell

    The > and | symbols can be modified to affect the standard error stream by appending a '&' character. So these commands do work:

      g++ myprogram.cpp >& compilation.log
      g++ myprogram.cpp |& more
    

    A useful program in this regard is tee, which copies its standard input both into the standard output and into a named file:

      g++ myprogram.cpp |& tee compilation.log
    

    Linux/CygWin, running bash

    The sequence "2>&1" in a command means "force the standard error to go wherever the standard output is going". So we can do any of the following:

      g++ myprogram.cpp 2>&1 > compilation.log
      g++ myprogram.cpp 2>&1 | more
    

    and we can still use tee:

      g++ myprogram.cpp 2>&1 | tee compilation.log
    

  2. Capturing compiler output

    There are other ways to capture the output of a compiler (or of any running program). You can run the compiler within the emacs editor, which then gives you a simple command to move from error message to error message, automatically bringing up the source code file on the line cited in the error message. This works with both the Unix and the MS Windows ports of emacs, and is the technique I use myself. vim, the vi improved editor will do the same.

    Finally, in Unix there is a command called "script" that causes all output to your screen to be captured in a file. Just say

       script log.txt
    

    and all output to your screen will be copied into log.txt until you say

       exit
    

    script output can be kind of ugly, because it includes all the control characters that you type or that your programs use to control formatting on the screen, but it's still useful.[40]

Understanding the Error Messages

Cascading One thing to keep in mind is that errors, especially errors in declarations, can cascade, with one misunderstanding by the compiler leading to a whole host of later messages. For example, if you meant to write

string s;

but instead wrote

strng s;

you will certainly get an error message for the unknown symbol strng. However, there's also the factor that the compiler really doesn't know what type s is supposed to be. Often the compiler will assume that any symbol of unknown type is supposed to be an int. So every time you subsequently use s in a string-like manner, e.g.,

s = s + "abcdef";

or

string t = s.substring(k, m);

the compiler will probably issue further complaints. Sometimes, therefore, it's best to stop after fixing a few declaration errors and recompile to see how many of the other messages need to be taken seriously.

Backtracking A compiler can only report where it detected a problem. Where you actually committed a mistake may be someplace entirely different.

The vast majority of error messages that C++ programmers will see are

  • syntax errors (missing brackets, semi-colons, etc.)

  • undeclared symbols

  • undefined symbols

  • type errors (usually cannot find a matching function complaints)

  • const errors

Let's look at these from the point of view of the compiler.

Syntax errors

Assume that the compiler has read part, but not all, of your program. The part that has just been read contains a syntax error. For the sake of example, let's say you wrote:

x = y + 2 * x // missing semi-colon

Now, when the compiler has read only the first line, it can't tell that anything is wrong. That's because it is still possible, as far as the compiler knows, that the next line of source code will start with a ; or some other valid expression. So the compiler will never complain about this line.

If the compiler reads another line, and discovers that you had written:

x = y + 2 * x // missing semi-colon
++i;

it still won't conclude that there's a missing semi-colon. For all it knows, the real mistake might be that you meant to type + instead of ++.

Now, things can be much worse. Suppose that inside a file foo.h you write

class Foo {
   Foo();
   int f();
   // missing };

and inside another file, bar.cpp, you write

#include "foo.h"

int g() {...}

void h(Foo) {...}

int main()  {...}

Where will the error be reported? Probably on the very last line of bar.cpp! Why? Because until then, it's still possible, as far as the compiler knows, for the missing \;} to come, in which case g, h, and main would just be additional member functions of the class Foo.

So, with syntax errors, you know only that the real mistake occurred on the line reported or earlier, possibly in an earlier-#include'd file.

undeclared and undefined symbols

When you forget to declare or define a type, variable, function, or other symbol, the compiler doesn't discover that anything is wrong until you try to use the unknown symbol. That, of course, may be far removed from the pace where you should have declared it.

type errors

When you use the wrong object in an expression or try to apply the wrong operator/function to an object, the compiler may detect this as a type mismatch between the function and the expression supplied as the parameter to that function. These messages seem to cause students the most grief, and yet the compiler is usually able to give very precise descriptions of what is going wrong. The line numbers are usually correct, and the compiler will often tell you exactly what is going wrong. That explanation, however, may be quite lengthy, for three reasons:

  1. Type names, especially when templates are involved, can be very long and messy-looking.

  2. Because C++ allows overloading, there may be many functions with the same name. The compiler will have to look at each of these to see if any one matches the parameter types you supplied. Some compilers report on each function tried, explaining why it didn't match the parameters in the faulty call.

  3. If the function call was itself produced by a template instantiation or an inline function, then the problem is detected at the function call (often inside a C++ standard library routine) but the actual problem lies at the place where the template was used/instantiated. So most compilers will list both the line where the error was detected and all the lines where templates were instantiated that led to the creation of the faulty call.

So, to deal with these, look at the error message on the faulty function call. Note what function/operator name is being complained about. Then look at the line where the faulty call occurred. If it's inside a template or inline function that is not your own code, look back through the instantiated from or called from lines until you get back into your own code. That's probably where the problem lies.

Here's an example taken from a student's code:

g++ -g -MMD -c testapq.cpp
/usr/local/lib/gcc-lib/sparc-sun-solaris2.7/2.95.2/../../../../include/g++-3/
stl_relops.h: In function `bool operator ><_Rb_tree_iterator<pair<const 
 PrioritizedNames,int>,pair<const PrioritizedNames,int> &,pair<const 
 PrioritizedNames,int> *> >(const _Rb_tree_iterator<pair<const 
 PrioritizedNames,int>,pair<const PrioritizedNames,int> &,pair<const 
 PrioritizedNames,int> *> &, const _Rb_tree_iterator<pair<const 
 PrioritizedNames,int>,pair<const PrioritizedNames,int> &,pair<const 
 PrioritizedNames,int> *> &)':
adjpq.h:234:   instantiated from `adjustable_priority_queue<
 PrioritizedNames,map<PrioritizedNames,int,CompareNames,allocator<int> >,
 ComparePriorities>::percolateDown(unsigned int)'
adjpq.h:177:   instantiated from `adjustable_priority_queue<PrioritizedNames,
 map<PrioritizedNames,int,CompareNames,allocator<int> >,
 ComparePriorities>::makeHeap()'
adjpq.h:84:   instantiated from here
 /usr/local/lib/gcc-lib/sparc-sun-solaris2.7/2.95.2/../../../../include/
 g++-3/stl_relops.h:43: no match for `const _Rb_tree_iterator<pair<const 
 PrioritizedNames,int>,pair<const PrioritizedNames,int> &,pair<const 
 PrioritizedNames,int> *> & < const _Rb_tree_iterator<pair<const 
 PrioritizedNames,int>,pair<const PrioritizedNames,int> &,pair<const 
 PrioritizedNames,int> *> &'

Now, that may look intimidating, but that's mainly because of the long type names (due to template use) and the long path names to files from the C++ standard library. Let's strip that down to the essentials:

g++ -g -MMD -c testapq.cpp
stl_relops.h: In function `bool operator >:
adjpq.h:234:   instantiated from `percolateDown(unsigned int)'
adjpq.h:177:   instantiated from `makeHeap()'
adjpq.h:84:   instantiated from here
stl_relops.h:43 no match for ... < ...

(This one is actually worse than most error messages, because it's easy to miss the < operator amidst all the <…> template markers.)

The problem is a no match for a less-than operator call in line 43 of a template within the standard library file stl_relops.h. But that template is instantiated from the student's own code (adjpq.h) and so the thing to do is to look at those three lines (234, 177, and 84) for a data type that is supposed to support a less-than operator, but doesn't.

const errors

Technically, const-ness is part of a type, so while sometimes these get special messages of their own, often they masquerade as ordinary type errors and must be interpreted in the same way.

Compiling Java in the Shell (Optional)

Java programs get compiled into object code for an imaginary CPU called the Java Virtual Machine (JVM). Consequently, you can't execute compiled Java code directly. You must run a program that simulates a JVM and let that simulated computer execute the Java code.

That may seem a little convoluted, but the JVM simulator is easier to write than a true compiler. Consequently, JVM simulators can be built into other programs (such as web browsers), allowing Java code compiled on one machine to be executed on almost any other machine. By contrast, a true native-code compiler (e.g., g++) produces executables that can only be run on a single kind of computer.

The command to compile Java code is javac (c for compiler) and the command to execute compiled Java code is java . So a typical sequence to compile and execute a single-file Java program would be

javac -g MyProgram.java
java MyProgram

Unlike most programming languages, Java includes some important restrictions on the file names used to store source code.

  • Java source code is stored in files ending with the extension .java .

  • Each Java source code file must contain exactly one public class declaration.

  • The base name of the file (the part before the extension) must be the same (including upper/lower case characters) as the name of the public class it contains.

So the command

javac -g MyProgram.java

compiles a file that must contain the code:

public
class MyProgram ...

The output of this compilation will be a file named MyProgram.class (and possibly some other .class files as well).

If we have a program that consists of multiple files, we can simply compile each file in turn:

javac -g MyProgram.java
javac -g MyADT.java

but this might not be necessary. If one Java file imports another, then the imported file will be automatically compiled if no .class file for it exists.

So, if the file MyProgram.java looked like this

import MyADT;

public
class MyProgram ...

then compiling MyProgram.java would also compile MyADT.java.[41]

Java programs come in two forms: applets and applications. Applets are placed on web servers and can be launched from HTML web pages so that the Java code runs whenever someone browses that page. Applications, on the other hand, are more like traditional programs that get invoked directly by the user.

To run a Java application, we use java:

java MyProgram

which looks for a file named MyProgram.class. Within that file, it looks for a compiled version of a function Main that must have been declared this way:

public static 
void main (java.lang.String[] args) {
  ⋮
}

and executes that function.

As Java programs get larger, programmers usually begin to group their classes into packages. Packages can also be grouped (nested) inside other packages. Again, Java has rules that cause the program's modular structure to be reflected in the file structure used to store it. If a class is declared to be inside one or more levels of nested packages, then each package name is used as a directory name when storing the source code file.

For example, if we had source code like this:

package Project.Utilities;

class MyADT {
 ⋮

and

package Project;;

import Project.Utilities.MyADT;

class MyProgram { 
   ⋮
  public static 
  void main (java.lang.String[] args) {
    ⋮
  }
}

then we would need a directory/file structure as shown in Figure 7.2, “Sample Java Package Structure”. Inside the Project directory would be the MyProgram.java file and another directory named Utilities, and the MyADT.java function goes inside that Utilities directory.

Figure 7.2. Sample Java Package Structure

Sample Java Package Structure


Now, here's the part that trips up many a Java programmer:

  • When you compile and execute code in Java packages, you must always do so from the directory at the top of the package structure.

  • The javac compilation command takes the name of the source code file.

  • The java execution command takes the name of the class containing the main function.

So the compilation and execution commands would be

javac -g Project/Utilities/MyADT.java
javac -g Project/MyProgram.java
java Project.MyProgram

For projects with many packages, the compiled code is often packaged up into a single file, called a jar, with a file extension of ".jar". This makes it easy to distribute an entire program as a single file. A jar file is actually a conventional "zip" compressed archive file with a little bit of extra directory information written into a special file (called the manifest) included in the archive.

A jar file can contain multiple programs. Sometimes there is a single preferred program specified in the manifest. If so, you can execute this program by simply saying

java -jar pathToTheJarFile

If there is no preferred program or you want to execute a program other than the preferred one, you use the normal java command to name the class containing the desired main function, but use the -cp option (described below) to tell the VM to look inside the jar file, e.g.,

java -cp myLargeProgram.jar Project.MyProgram

As with g++, there are several options that you may choose to employ when compiling and executing Java code. Here is a summary of the most commonly used ones:

Compilation Flags
-cp pathlist Add the directories and jar files named in the pathlist (multiple items may be separated by ':') to the list of places searched when import ing other Java source code.
-g Include debugging information in compiled code (required if you want to be able to run the gdb debugger.
-depend Check each imported class to see if its source code has been changed since it was last compiled. If so, automatically recompile it.
-deprecation Check the code for features that used to be legal in Java, but are expected to become illegal in the near future.
-O Optimize the compiled code (produces smaller, faster programs but takes longer to compile)
Execution Flags
-cp pathlist Add the directories and jar files named in the pathlist (multiple items may be separated by ':') to the list of places searched when importing other compiled Java .class files.



[35] Actually, gcc and g++ are the same compiler being invoked with slightly different options.

[36] There are variations, particularly for C++. Other less common endings accepted by some C++ compilers for non-header files include .C .cc and .cxx .

[37] You can modify your execution path, if desired, to add additional directories.

[38] Zeil's 1st Rule of Debugging: Never remove debugging output. Just make it conditional. If you remove it, you're bound to want it again later.

Zeil's 2nd Rule of Debugging: Never leave your debugging code active when you submit your programs for grading. If the grader is using an automatic program to check the correctness of the output, unexpected output will make your program fail the tests. On the other hand, if the grader is reading the output to check its correctness, wading through extra output really ticks the grader off!

[39] A note for CygWin users: By default, CygWin gcc and g++ produce Windows console applications --- applications designed to run from within a shell (bash or the Windows command line tool). They can, however, produce GUI applications with windows, menus, etc., by using -l to link in the windowing libraries. The list of libraries involved is rather long, so a shortcut option is provided: -mwindows.

Programming windowing code is a fairly involved process. I suggest getting a library that simplifies this process for beginners. The V library is a good choice, and has the additional advantage that code written for use with V can be compiled to produce either Microsoft Windows or Unix X windows programs.

[40] In particular, if you ever want to capture both the output of some program AND the stuff you typed to produce that output (e.g., so you can send it in an e-mail to someone saying "what am I doing wrong here?"), then script is the way to go.

[41] Beware, however, after the first compilation. javac by default only checks to see if an appropriately named .class file exists. If you subsequently make changes to MyADT.java and then recompile MyProgram.java, the compiler will not realize that MyADT.java needs to be recompiled as well.