Notes 0: Compiling, Makefiles, Debugging

If you have already been compiling stuff under Unix, this will probably be review. However, for ease of grading, we would like you to all use Makefiles (all the time) so that we don't ever have to deal with building binaries where strange command line args are required.

Platform

Since we have to grade a gazillion programs, we want to be able to build everything on the HP-UX platform. If it doesn't build under HP-UX, we're going to send it back to you until it does. Personally, I've found that almost everything I write under Linux is portable to HP-UX with a minimum of hassle, so I'd encourage all of you to reformat that pesky Windows95 disk and install Linux immediately so you can use it for assignments. (Of course, I am not responsible for any data loss that might occur.)

Of course, I'm not really serious. You might need Windows95 to play the latest video games. But I did want to point out that Linux and HP-UX are more the same than not, and porting your finished program to the latter won't be that big of a deal. I'll try to point out any differences between the two as I discover them. I encourage all of you to do the same.

Mac users who want to mess with Linux should check out MkLinux. But I really don't know anything about this one.

Editor

We all know that vi variants are the fastest editors on the planet. For those of you who prefer to slow down and smell the pixels, emacs and pico will serve you well. :-)

Ok, ok, you got me. I just wanted to taunt emacs users for a bit. We really don't care what editor you use.

Compiling

Under HP-UX, if you want to build an ANSI C program (and we assure you that you do), you must use the "-Ae" switch with the "cc" compiler. Or you could use the GNU ("Gnu's Not Unix") "gcc" to build, since it's an ANSI compiler already:

$ cc -Ae -o foo foo.c
or
$ gcc -o foo foo.c

The HP-UX compiler is slightly faster and produces slightly smaller code. But I know that some of us get some kind of thrill from using GNU software, so, basically, use whatever ANSI C compiler you want*.

* As long as it's under Unix, does not contain the word "Microsoft" (unless it's used in a derogatory sense).

In Excalibur, Merlin says, "Good and evil--there never is one without the other." Which leads me straight to:

Did someone say "C++"? Well, we certainly prefer not. You won't find any nice C++ classes with all these Unix internals in there. Of course, you could write C++ code that simply calls these routines...but to make a long story short, that would violate the spirit of Unix systems programming. Besides, unless you're going for pure OOP (and we're not), C++ is bad. If I may quote myself:

They say C gives you just enough rope to hang yourself. If that's the case, then C++ gives you just enough rope to hang yourself and all your friends.

With that out of the way, we're ready for more serious things.

Makefiles

Haven't you ever used "make"? It's kind of the super programmers' tool of all time. Books have been written about it. (I recommend the O'Reilly book. I think it has a lemur on the cover.) I will not reproduce one here, but instead will give you the bare minimum you will need to write a Makefile and use it.

To learn by example, lets say that you have a source file "foo.c" and a header file called "foo.h". The foo.c program uses some information from the header file, so we want it to be rebuilt whenever the header file changes (or whenever the file foo.c changes). It can be said that the foo binary is dependent on the two files foo.c and foo.h.

Now, edit a file called "Makefile" in the same directory that your source (foo.c) is in. Make it look just like this:

    # My first Makefile (maybe)
    foo: foo.o
            cc -o foo foo.o

    foo.o: foo.c foo.h
            cc -c -Ae foo.c

This Makefile says the following: program "foo" is dependent on "foo.o". Therefore, if foo.o is changed, I must execute "cc -o foo foo.o". Likewise, foo.o itself depends on foo.c and foo.h. If either of them has changed since the last rebuild, I must now execute "cc -c -Ae foo.c".

So how do you make it work? Simply type "make" on the command line. Et Voila. It builds everything for you. And another thing: if you only want to build foo.o, you can type "make foo.o" and it will do only that. If you don't give an argument to make, it will simply use the first rule and go from there.

Now, if all of you have a nice Makefile for your programs, we can be maximally lazy and only need to hit five keys to build your projects.

Debugging

I generally don't ever use a debugger (I rely on diagnostic printf()s for the most part), but I'll give a quickstart overview of the GNU debugger for those of you who want to mess with it some more.

Given the following program, "foo.c":

    main()
    {
        int a = 1;
        a++;
        a--;
        a += 4;
        a = 9;
    }

compile it with the following command:

   cc -g -Ae -o foo foo.c

The "-g" switch tells the compiler that you want to include symbolic debugging information in your program. This is what will enable the debugger to talk back to you in C instead of assembly.

Next, start the debugger (treat the following as a transcript--things you type are in bold, my comments are in italics):

    $ gdb foo
    GDB is free software and you are welcome to distribute copies of it
     under certain conditions; type "show copying" to see the conditions.
    There is absolutely no warranty for GDB; type "show warranty" for
    details.
    GDB 4.16 (i486-slackware-linux), 
    Copyright 1996 Free Software Foundation, Inc...
    (gdb) b main  [Set a breakpoint at function main]
    Breakpoint 1 at 0x804840a: file foo.c, line 3.
    (gdb) r       [Run it!]
    Starting program: /usr/local/home/beej/public_html/298C/foo 

    Breakpoint 1, main () at foo.c:3
    3               int a = 1;
    (gdb) s       [Step through the above line!]
    5               a++;
    (gdb) p a     [Print the value of the symbol "a"]
    $1 = 1
    (gdb) s
    6               a--;
    (gdb) s
    7               a += 4;
    (gdb) s
    8               a = 9;
    (gdb) p a
    $2 = 5        [This, since the last line executed was "a += 4"]
    (gdb) s
    9       }
    (gdb) p a     [Now a == 9]
    $5 = 9
    (gdb) s
    0x80483bb in ___crt_dummy__ ()
    (gdb) s
    Single stepping until exit from function ___crt_dummy__, 
    which has no line number information.

    Program exited normally.
    (gdb) quit
    $

That was pretty bare-bones as far as the debugger goes. Here are two more pointers:

  1. If you type "help" from the "(gdb)" prompt, you will get all kinds of knowledge. This is a must-do.

  2. If your program, say "foo" dumps core and you compiled it with the "-g" switch, you can type:

        gdb foo core
    

    and it will tell you where your program crashed. Spooky.

Don't ask too much about the debugger, since we're not pros with it. I just wanted to let you know that it was available.

man pages

The single greatest source for information regarding any of the functions we're going to cover in this class is the online man pages. They have complete descriptions of all the calls, as well as prototypes and the occasional example.

A caveat: some man pages aren't what you hoped they are, since man gave you a result from the wrong section. For instance,

    $ man write

gives you information on how to write to other people's terminals--probably not what you wanted.

    $ man 2 write

gives information out of section 2 (System Calls) on the write() call, which is more to the tune of this class.