C Programming Tips and Tricks
Abstract
This document is the result of me being thrown back into C programming after a few years in absense. 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 🙂
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:
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.
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 */
/* accept command line args */
pid = fork();
if( pid == 0 ) {
/* this is the "background" process. Execute process loop here */
}
else {
/* "foreground" process exits */
exit(0);
}
}
This makes the program duplicate itself into a “parent” and “child” process. The PID returned for the child will be 0, while the parents PID will remain the same (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 does no forking into the background and prints 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 segfaults 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.
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 */
while(1) {
/* process loop that goes on forever */
}
}
static void catch_sigint( int signo ) {
Log("Caught SIGINT, exiting...");
/* final cleanup stuff */
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.
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 comments with “##” for easy search and removal. This is a good idea. I also like to keep lots of if( debug ) printf(“…”)‘s around as well, so that when my program is started with the “-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().
- Remove large sections of code for with #ifdef QQQ. This is another suggestion from C Elements of Style, and a good one. Instead of commenting out, or worse, deleting, wrap it as:
#ifdef QQQ /* code that you don't want to run */ #endif QQQ
A couple of notes:- If you don’t like QQQ (suggested for the fact there is little chance of it being defined and it’s search-ability), use #ifdef UNDEF, which 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. Some older compilers may puke on this however. If they do, just stick with plain old #endif
- You can resort to using exit(0) to mark points of arrival. 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(0) in. If the program exits with error code of 0, you’ll know it hit your statement. Linus Torvalds used a simliar technique when creating the first linux video drivers in assembler. He’d put reboot commands in and if the system just froze he knew his reboot statement hadn’t been reached.
This is the Gnu DeBugger. It rocks. I don’t know a whole lot about it, but here is what I 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 args that you’d like to pass to “progname”). Gdb also mimicks a shell in that typing “make” at the (gdb) prompt will execute make in the current directory. Result? If you have a makefile it gets run. Other result? One less xterm needed for development.
When in the shell, your program could puke on 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/ulisten/./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 (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 memory it 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 most memory errors (YMMV of course 🙂 ).
There are two options you can use with Electric Fence, both controlled by environmental variables:
F_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 set areas of memory that you free() with protected memory, thus not allowing you to use memory that has been free()d. This helped me fix a potentially nasty bug in something like the following code:
SafeStrncpy( tokenbuffer, strstr( buffer, "=" )+1, MAXBUF );
tokenbuffer[MAXBUF - 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.
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. It must exit cleanly to work (see section on signal handling for how to deal with this and servers/daemons).
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 $@
lt; -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 in the distribution.
After you have run your program you’ll find something like this in the file memwatch.log:
============ 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 73 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.
- http://www.google.com – great place to put in search strings like “network programming in c” or “pid_t explanation” to get answers.
- http://www.ecst.csuchico.edu/~beej/guide/net/ – Beej’s Guide to Network Programming, good stuff for C and sockets.
- 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 (old and new)