Some practices that can help save effort

Use gcc's -Wall switch to generate as picky a set of warning messages as possible.
This switch will point out many problems that are not always obvious and easy to track down. You should try to eliminate as many of the warnings and error messages as possible before asking for help. I REFUSE to look for logical errors or segmentation violations in programs where there are any warnings or errors listed by -Wall. You may ask what a message means, and what the causes may be, but I won't help you if you ignore these messages. The vast majority of (non-logical) problems that students have had in past courses are pointed out clearly in the warning messages generated by -Wall.
Attack the first few and most obvious warning/error messages, then recompile.
In a long list of messages, you need to concentrate on the start of the list and work down. Later errors may be due to the parser being thrown off track by an earlier error, and may not be actual problems with your program. By attacking the top of the list first, you do not waste time searching for non-existent errors. Continue down the list until the errors stop making sense, then continue by searching for obvious errors such as missing semicolons in the rest of the list. After this, recompile and start at the top of the list again. This will cut down on the number of compile cycles used to fix coding errors.
Design code top-down and implement bottom-up.
Designing top-down allows you to think in terms of the big picture while determining the requirements of low-level routines. Implementing from the bottom-up allows you to clearly define the interface to low-level routines, their error modes, and their return values. By making the low-level functions as well-defined as possible, you save yourself trouble debugging higher-level routines later because you know exactly what the low-level routine is supposed to do.
Understand when to (and NOT to) use debuggers such as gdb.
Debuggers can help narrow the search for an error quickly and show much of the information above. They do not, however, generally work well in multi-process or multi-threaded environments. They also don't help when debugging things written in languages they don't understand (such as PCCTS).
Place debugging fprintf statements within your code at strategic locations.
fprintf's can be used to display information about variables, function entries and exits, and function arguments and return values. You should fflush() the stream immediately afterward, otherwise there is no guarantee that you will see the message if you program crashes. This code should be surrounded by conditional compilation directives to allow the debugging code to be stripped from the compilation once the initial debugging process has been completed without removing all the lines of code from your source. For example:
   
	#define DEBUG_FUNCS

	void thisfunc ( int f, char *e )
	{
		int g;

	#ifdef DEBUG_FUNCS
		fprintf ( stderr, "Entering `thisfunc'.  f=%d, e=%s\n", f, e );
		fflush ( stderr );
	#endif

		/* Body of function */

	#ifdef DEBUG_FUNCS
		fprintf ( stderr, "Leaving `thisfunc'.  g=%d\n", g );
		fflush ( stderr );
	#endif
		return ( g );
	}
   
   

This page was last modified .