TFM:The C Chapter

From ProgSoc Wiki

Jump to: navigation, search

Contents

The C Chapter

Ryan Chadwick and Murray Grant[1]

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 BScIT) 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:

  • Buy a text book.
  • Search the Internet.
  • Wait until you do Procedural Programming[2].

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:

niflheim$ cc <options> <files>

or

niflheim$ gcc <options> <files>

The output file will be a.out[3] unless you use the -o option:

niflheim$ cc <options> <files> -o <output file name>

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.

niflheim$ 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 <stdio.h>

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

Then call the compiler and run the program:

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

A Slightly Harder Program

This program introduces while loops, if statements and getting input from the user[4].

/* draws a square */
#include <stdio.h>

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:

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

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 bug[5]. 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 to[6]. 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 line[7] 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 and breathe a sigh of relief. Sometime later you decide to show off your masterpiece to a friend:

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

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 loops[8], 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 things[9] that you would do better to ignore. The -flags option will display various extra options. Lint should be invoked as such:

niflheim$ lint -Nlevel=4 file

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 should be checked out by someone.). 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:

niflheim$ gcc -g file.c
niflheim$ gdb a.out
GNU gdb (GDB) 7.1
Copyright (C) 2010 Free Software Foundation, Inc.
...
(gdb) run
Starting program: /home/user/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:

niflheim$ gcc -g file.c
niflheim$ ./a.out
Segmentation fault (core dumped)
niflheim$ gdb a.out core
GNU gdb (GDB) 7.1
Copyright (C) 2010 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 [10], 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 compiler[11]. The following example links a program with the math library (m):

niflheim$ gcc sine.c -lm

Optimisation

Any serious hacker wants their 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 3[12] as such:

niflheim$ cc -O2 <file>

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:

niflheim$ gcc <file> -pg
niflheim$ ./a.out
niflheim$ gprof > profile.out

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


  1. Updated by Thomas Given-Wilson.
  2. Or whatever the C subject is called these days (Embedded C -- but only if you are undertaking the ICT or Electrical Engineering programme. Like a lot of subjects in FEIT, it has very little to do with using the C language in embedded systems. For that, you will need to do Embedded Software. If you're a BScIT or BIT student, none of this will apply to you, since you're all learning Java as part of Programming Fundamentals these days... ("A chapter on Java", you suggest? Heresy!))
  3. For obscure and arcane technical reasons that date back well before yesteryear.
  4. Always, always, always validate your input. You never know when someone will enter -2847713.824.
  5. There are rumours of sleeping, dead and even arts students finding obscure bugs.
  6. Ever wondered what the minus one-th element of an array is? Simply refer to it as array[-1] and find out!
  7. This is where the compiler gave up interpreting the poor excuse for code it was given.
  8. I wonder why my program has been running for 3 hours...
  9. Who really checks the return value of printf()... come to think of it, what is the return value of printf()!
  10. Try coding a device driver, or a cross platform application, or a defragmenter, or a multi-threaded image manipulation program, or....
  11. There are legitimate reasons for this but just trust me, it will save you much bother.
  12. -O3, in the GNU compiler, will attempt reorder your code for better performance.
Personal tools