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