Back to table of contents

The C Chapter

Ryan Chadwick and Murray Grant

Getting started with C

C is a very nice language to learn (especially compared to some of the other languages that you will come across in first year comp sci) and is also quite useful. It is an extraordinarily powerful language - wielding such power requires responsibility. This guide is intended only to provide a taste for C, if you want to learn more there are several options available:

Compilers

There are two compilers available on the Faculty computers, cc, which compiles ``traditional'' C, and the GNU compiler gcc, which compiles both ``traditional'' C and ANSI C. Both work in much the same way. You run the compilers as follows:

sally$ cc  

or

sally$ gcc  

The output file will be a.out31.2 unless you use the -o option:

sally$ cc   -o 

The most useful option is -Wall. No, it has nothing to do with that which keeps the roof from falling down. It tells the compiler to display all warnings, rather than the default of a small subset. This can save you a great deal of trouble when writing more advanced programs.

Both of the above mentioned compilers are able to compile code contained within several files. This is done by calling the compiler and listing the names of the relevant files, separated by spaces. This allows some rudimentary object-orientated-ish encapsulation.

sally$ gcc input.c process.c output.c

A Simple Program

As is tradition with learning programming languages our first program is going to be the ``hello world'' program. A simple program to illustrate the structure of a C program and also show basic output.

Create a file hello.c containing the following:

#include 

main()
{
	printf("Hello world. \n");
}

Then call the compiler and run the program:

sally$ gcc hello.c
sally$ a.out
Hello world.
sally$

A Slightly Harder Program

This program introduces while loops, if statements and getting input from the user31.3.

/* draws a square */
#include 

int main ()
{
    int length, width, count, height;

    printf ("please enter the width and depth of the block to be made:");
    scanf ("%d%d", &length, &width);

    count = 1;
    height = 1;

    while (height <= width)
    {
        while (count <= length)
        {
            if (((height == 1) || (height == width)) ||
                    ((count ==1) || (count == length)))
                printf ("#");
            else
                printf (" ");
            count++;
        }
        printf ("\n");
        height++;
        count = 1;
    }
    return 0;
}

And compile and run as before:

sally$ cc test.c
sally$ ./a.out
please enter the width and depth of the block to be made: 5 3
#####
#   #
#####
sally$

Finding bugs

When compiling programs it isn't uncommon for bugs to exist. Often they can be simple syntax errors that are really hard to find but quite obvious once found. The most common syntactical errors are missing semi-colons (;), forgotten parenthesis (()) or braces ({} and unmatched comment symbols (/* */). One of the best ways to find the harder bugs is to get a friend to look over your code. The more uninterested the friend, the more likely they will be to find your bug31.4. If friends are in short supply just leave your code for a while, go do something totally unrelated to programming or computers, then come back and look at your code again.

Error Messages

Error messages in C don't tend to be the most helpful, and the compilers will generally let you get away with many things that you probably shouldn't be allowed to31.5. Errors can be divided into two general categories.

Compiler Errors

These sort of errors are the good sort because the compiler finds them for you. It looks at the mess of code you gave it and tells you something along the lines of ``you've gotta be joking'', followed by an equally useful error message. When such an error is encountered the message displayed will include the name of the file, the line number and a brief description (which can range from helpful to downright cryptic). Often the error is not on the reported line31.6 but several lines (or pages) before.

Runtime Errors

By some stroke of genius, you managed to compile your program before the deadline. Quickly you submit the file for marking via e-mail and breath a sigh of relief. Sometime later you decide to show off your masterpiece to a friend:

sally$ ./a.out
Segmentation fault (core dumped)
sally$

The most likely cause for core dumps is the misuse of pointers. Pointers are powerful yet dangerous things. Luckily for you the system is smart enough to catch most errors of this nature - giving the error shown above. The ones that it can't catch are infinite loops31.7, or more subtle pointer related things like overrunning array boundaries. Good luck splatting your bugs!

Lint

The lint program may help your never-ending search for bugs. It analyses your program and points out some of the more obscure things you've done (or should have done). You should run lint on a source file that compiles without warnings. Lint will tell you to do certain things that you don't really have to do, but should be doing. Unfortunately, it will also pull up way too many silly things31.8 that you would do better to ignore. The -flags option will display various extra options. Lint should be invoked as such:

sally$ lint -Nlevel=4 file.c

The Debugger

When you really can't work out what is going on in your program, you can resort to using a debugger. A small step needs to be taken first though: tell the compiler you're going to use a debugger using the -g option. There are two ways to invoke the debugger, on a program or on a program with a core dump. The second option will show you exactly what the state of your program was when it crashed (including which line of your code it crashed on). The GNU debugger gdb will work on programs compiled with cc or gcc . There is even a graphical interface to it (which wasn't working last time I checked). It has a plethora of options, the most important of which are in the breakpoints, data and running categories.

A few examples of starting the debugger on a program that dumps core:

sally$ gcc -g file.c
sally$ gdb a.out
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
...
(gdb) run
Starting program: /home/mujgrant/a.out

Program received signal SIGSEGV, Segmentation fault.
0x804848c in main () at file.c:9
9               foo[0] = '\0';
(gdb)

And if you run the program and it has left a core file behind:

sally$ gcc -g file.c
sally$ ./a.out
Segmentation fault (core dumped)
sally$ gdb a.out core
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
...
Core was generated by `a.out'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /usr/lib/libc.so.4...done.
Reading symbols from /usr/libexec/ld-elf.so.1...done.
#0  0x804848c in main () at file.c:9
9               foo[0] = '\0';
(gdb)

Advanced Stuff

Writing a program that uses a linked list isn't the hardest thing to do in C 31.9, but there are a few other things that might be useful (and fun) to know.

Libraries

Every so often you'll find a function you need is in another library. For example: maths related functions are all located in the maths library. To include a library other than the standard C library (included by default) use the -l<library> compiler option. The manual page for a function will generally tell you which library it is in. Always put the libraries at the end of your arguments to the compiler31.10. The following example links a program with the math library (m):

sally$ gcc sine.c -lm

Optimisation

Any serious hacker wants his (or her) program to run as fast as possible. Compiler optimisation allow certain parts of a program to be rewritten slightly, but to run faster (or using less memory). Compilers support various levels of optimisation by using the -O option. Other options can be selected using the -f option, see the manual for your compiler for more specifics. The -O options can also be used in conjunction with a number (higher numbers optimising more) between 2 and 331.11 as such:

sally$ cc -O2 foo.c

Before you recompile all your programs to -O3, bear in mind that these optimisation are unlikely to bring significant performance benefits but are very likely to make your program much larger and take longer to compile. There is no compiler that will be able to optimise a linear search into a binary search, or a bubble sort into an insertion sort; changing to a better algorithm will bring the most benefit.

Profiling

Profiling goes hand-in-hand with serious attempts at optimisation. A profiler generates a file that tells you how often your program is executing each function, so you can optimise the most commonly used section of code. To profile your code you have to tell the compiler, just like debugging, using the -pg option. Once you run your program you need to run gprof to decode the raw output file. Eg:

sally$ gcc file.c -pg
sally$ ./a.out
sally$ gprof > profile.out

This generates over 400 lines of output on a simple hello world program. So it would be well worth investigating the options associated with profiling if you ever intend to use it seriously.

Links and Books

Back to table of contents