C Programming Tips and Tricks

This is the result of me being thrown back into C programming after a few years in absence. When I was in school I learnt basic C, C++ and programming under Windows. In my current job I was required to re-write some Perl code in C. Following are some things that I stumbled on and that I feel others could benifit from. Learn through my failures so to speak :)

The following is only roughly organized, but hopefully accurate. Feel free to add to this node or point out any mistakes to me by /msg.

Conversions

Be careful when doing conversions from strings to numbers, and ensure that you are using the correct function. At one point I wrote some code that looked like this:

unsigned long bytes;
...
bytes = (unsigned long)atoi( s );  /* save bytes for later */

Even though I was casting as a long, the atoi() function converts to int, and not long. The code worked for a fair amount of time, until the value stored in s grew more than the size of an int, and then suddenly 0 was returned. The atoi() will not produce an error on fail, but simply bails and returns 0. To solve this problem use atol() instead.

Fork()ing, Daemons and Servers

At some point you will be called to write a server, daemon, or some program that simply sits and waits for input, responds to it, waits for more input, responds, etc...

You could write something and then run it with the & to put it into the background, but why not make it do it by itself?

To make a program put itself in the background and going into "daemon mode" do the following:

void main( int argc, char** argv )
{
	pid_t pid = 0;
	/* set stuff up */
	
	pid = fork();
	if( pid == 0 ) {
		/* this is the "background" (child) process.  Execute process loop here */
	}
	else {
		/* "foreground" (parent) process exits */
		exit(0);
	}
}

This code snippet shows how to make the program duplicate itself into a "parent" and "child" process. The PID returned for the child will be 0, while the value returned to the parent will be the PID of the child process (something != 0). If the PID is 0, continue to process, if not, exit. The program will run in the background just like a good daemon should!

Some other tips for doing this would be:

  • Supply command line arguments such as "-debug" and "-nofork". The first one would tell the program not to fork into the background and print out any debug messages, while the second one prints no debug messages, but does not fork. This is especially handy if you have a lot of debug messages, but are having specific problems such as a segfault or some other error that does not show up when put into daemon mode. I had one nasty bug due to a ill-defined hostname. When I ran my daemon, it simply exited (as it should) but nothing was left running in the background! Running it with -nofork showed me the error message immediately.
  • Use while(1){...} as your main process loop, not for(;;){...}. Both do exactly the same thing, but the while(1) is more intuitive for someone (perhaps even you) reading the code in the future.
  • One of the big things is that if you have clients connecting to your server and then disconnecting, you'll get a bunch of defunct processes. There is a lot of code out there that has examples of how to avoid this (perhaps I'll put that in here later).

Signals

Mentioned later, sometimes you'll want a program to exit cleanly, instead of killing it with CTRL+C. This is fine for normal programs, which run from start to finish, but what about servers, which are designed to run forever? Try adding something like this:

#include <signal.h>

static void catch_sigint( int );

int main( int argc, char** argv ) 
{
	struct sigaction sa_old;
	struct sigaction sa_new;

	// set up signal handling
	sa_new.sa_handler = catch_sigint;
	sigemptyset(&sa_new.sa_mask);
	sa_new.sa_flags = 0;
	sigaction(SIGINT, &sa_new, &sa_old );

	/* continue on */
	while( 42 ) {
		/* process loop that goes on forever */
		...
	}
}

static void catch_sigint( int signo ) {
   Log("Caught SIGINT, exiting...");
 	/* any final cleanup code to free globally allocated memory and whatnot */
   exit (1) ;
}

In this case, the program will loop forever, until you hit CTRL+C (SIGINT) and then it will execute the catch_sigint() function and do whatever you have in it. You could also use this to make sure your program does not die on the user hitting CTRL+C.

Debugging

There are many different ways to do debugging, and many different methodologies to it. Some things I suggest (and Your Milage May Vary) would be:

  • Lots of printf() statements. C Elements of Style suggests prefixing all debug output with "##" for easy search and removal. This is a good idea. However, I also like to keep lots of "if( debug ) printf("...")"'s around as well, so that when my program is started with a "-debug" flag, useful information is displayed. Either way, if you don't know where something is crashing, or what the value is in a variable (never mind what it should be), use a printf().
  • Another method I recently came across was in the Samba source code. They have a function (actually a macro) that takes a debug level argument and a string. This way you can put statements like "DEBUG( 3, ("foo = %s", val ));" This is a great method for trimming debug output to the level you actually need, without recompiling.
  • Remove large sections of code for with "#ifdef"s. This is another suggestion from C Elements of Style, and a good one. Instead of commenting out, or worse, deleting, wrap it as:

    #ifdef UNDEF
    	/* code that you don't want to run */
    #endif UNDEF
    

    A couple of notes:

    • If you don't like UNDEF (suggested for the fact it should never be defined!) as well as it's search-ability), try #ifdef QQQ, which is another favorite for a value that should never be defined.
    • The QQQ on the #endif is not needed, and is only there for a visual reminder of what you are ending. In fact, with gcc 3.0 this code will fail! You will have to end the #ifdef with "#endif /* QQQ */"
  • You can resort to using exit(0) to mark points of execution in your code. If you have some particularily odd code (graphics, games or something) and you are having trouble tracking down exactly where the code is getting to, stick an exit(10) in. If the program exits with exit code of 10 (or whatever value you choose), you'll know it hit that statement. Linus Torvalds used a simliar technique when first writing Linux. He'd put reboot assembler commands into his code. If the system just froze he knew his reboot statement hadn't been reached.

gdb

This is the Gnu DeBugger. It rocks. I don't know a whole lot about it, but here are some simple things you can do with it.

Compile your programs with -ggdb this makes a large binary, but the size is because of extra information included specifically for gdb.

Run your program with $ gdb ./progname. You'll be presented with a (gdb) prompt. Type run -args (where -args are any command line arguments that you'd like to pass to "progname"). gdb also mimicks a shell in that typing "make" at the (gdb) prompt will execute the program make in the current directory. Result? If you have a Makefile it gets run. Other result? One less xterm needed for development.

When in gdb, your program could puke with a segfault. If so, type "where" or "bt" (for backtrace) and you'll get a list of the tree of commands executed, ending with the one causing the segfault. Something like:

(gdb) r
Starting program: /home/alan/code/test
Hostname Enterprise
gethostbyname: Unknown host

Program received signal SIGSEGV, Segmentation fault.
0x804863a in get_my_ip () at test.c:36
36    return( inet_ntoa( *(struct in_addr *)hp->h_addr ) );
(gdb) where
#0  0x804863a in get_my_ip () at test.c:36
#1  0x8048662 in main (argc=1, argv=0xbffff904) at test.c:40
#2  0x40042bcc in __libc_start_main () from /lib/libc.so.6
(gdb)

This shows you that at line 36 in test.c something went wrong. This happened in get_my_ip which was called from main() on line 40, and so on.... If you're doing hardcore stuff and have the debug versions of libc you'll get information on where stuff broke in there too. You'll see that the only line numbers displayed were in the test program, as that's the only thing compiled with -ggdb.

Electric Fence

Electric fence (ftp://ftp.perens.com/pub/ElectricFence) is a malloc() debugging library written by Bruce Perens. It rocks. Basically how it works is this: When you allocate a space in memory for your data Electric Fence allocates protected memory just after. If you have a fencepost error (running off the end of an array) your program will immediately exit with a protection error. Combining this with gdb you can track down a large number of memory errors (YMMV of course :)

There are two useful options you can use with Electric Fence, both controlled by environmental variables:


EF_PROTECT_BELOW=1
EF_PROTECT_FREE=1

If EF_PROTECT_BELOW is set to 1 then Electric Fence will allocate protected memory below your malloc()'d memory as well as above. This will help protect you from running off the other side of your arrays. EF_PROTECT_FREE, when set to 1, will replace areas of memory that you free() with protected memory, thus not allowing you to re-use memory that has been free()d. This helped me fix a potentially nasty bug in something like the following code:

strncpy( tokenbuffer, strstr( buffer, "=")+1, MAXBUF );
tokenbufferMAXBUF - 1 = '\0';
if( tokenbuffer ) {
   free( tokenbuffer );
}
ret = atoi( tokenbuffer );

Electric Fence immediately died when it hit the last line of code, as atoi() was trying to use free()d memory.

Combining Electric Fence with gdb, you can track down to exactly what line tried to access the protected memory that you weren't supposed to diddle with.

Memwatch

This is another excellent utility for detecting memory leaks. Like Electric Fence, it replaces malloc() and free() (and calloc(), and realloc(), etc) with it's own functions that keep track of memory. Just compile in memwatch.c to your program (shown below) and when run it'll produce a report of leaked memory.

Note: this report will not work if the program crashes. You code must exit cleanly to work.

You can retrieve memwatch from http://www.linkdata.se/sourcecode.html. It is very simple to use. Just compile the .c file in with your program with a couple of variables set.

Example Makefile:

CC=gcc
CFLAGS=-ggdb -Wall

test: test.o
$(CC) -o $@ $< -DMEMWATCH -DMEMWATCH_STDIO memwatch.o

test.o: test.c
$(CC) -c $*.c -DMEMWATCH -DMEMWATCH_STDIO memwatch.c

The above will compile the memwatch program with the MEMWATCH and MEMWATCH_STDIO flags set (which as far as I can tell, make it work). Full instructions are in the USING file included in the distribution.

After you have run your program you'll find something like this in the file memwatch.log, residing in the directory you ran the program from:

============ MEMWATCH 2.64 Copyright (C) 1992-1999 Johan Lindh =============

Started at Tue Oct 31 23:17:30 2000

Modes: __STDC__ 32-bit mwDWORD==(unsigned long)
mwROUNDALLOC==4 sizeof(mwData)==32 mwDataSize==32

NULL free: <290> test.c(1838), NULL pointer free'd

Stopped at Tue Oct 31 23:18:30 2000

unfreed: <31> test.c(1440), 8 bytes at 0x401a0ff4     {53 F5 CC 01 00 00 00 (deleted) }
unfreed: <4> test.c(1359), 132 bytes at 0x4019af78    {70 69 6E 67 3 00 00 (deleted) }

Memory usage statistics (global):
 N)umber of allocations made: 147
 L)argest memory usage      : 8332
 T)otal of all alloc() calls: 131968
 U)nfreed bytes totals      : 140

The contents are pretty self explanitory. If you free() an already free()'d or NULL pointer, it points it out. Ditto for unfreed memory. The section at the bottom shows you stats with how much memory was leaked, how much was used, total allocated, and so on.

Some Useful Links

Books

  • Linux Socket Programming by Example
  • Linux Application Development
  • Teach Yourself C in 21 Days
  • Unix Network Programming (Stevens)
  • Crash Course in C (QUE pocket reference)
  • Beginning Linux Programming

Log in or register to write something here or to contact authors.