Debugging


A Generic Command-Line Switch

William McMahon


William McMahon has been programming professionally for 10 years, and programming in C for six years on various microcomputer platforms. He is currently unemployed.

Introduction

C programs frequently use command-line parameters to override default values, display help, and control special options. Whatever their use, command-line parameters are almost always pre-defined — for the command line to be parsed, the program must know what to expect. This hard-wired rigidity is appropriate for switches that control the main features of a program since these features aren't likely to change materially during the program's life. But, especially for controlling debugging options, a more flexible method would be nice.

To monitor variables, for instance, programmers often add a print statement at a certain place in the code. Combining a special switch variable, (debug in this example), with a simple test disables the print statement during normal execution and activates it during test runs. For example:

if (debug)
   print("\n %s hashes into %d ", key, hash);
The program examines the command line, looking for a special flag. If the flag exists, it sets debug to TRUE. However, in a large system where you may need to control independent switches, an explicitly-coded test for each switch becomes quite cumbersome. In such environments you need a more systematic approach.

The most commonly used approaches pre-define a fixed number of switches. For example, using bit operations you could pack several switches into one switched variable:

if (debug & BIT_MASK_1)
   printf ("Whatever");
This approach conserves memory and simplifies the command-line processing since a single numeric parameter can represent many switch settings. While such approaches are more flexible, ultimately they are still limited to some pre-determined, finite number of switches. Moreover, they require the programmer to keep track of the relationship between each switch and the statement it controls.

Multiple Command-Line Switches

Instead, I use a straightforward method that can control an unlimited number of switches from the command line, and that automatically identifies each statement by its location in the source code. With this method you'll never run out of switches. As long as the source code is available, you can readily identify which switch you need to set for a particular test.

Overview

While this method is more complicated than using a bit field, it requires just two functions: one to parse the command line, the other to serve as the switch. Instead of defining individual switches for each statement, I identify areas of code to be switched on or off. Determining whether a switch is on becomes a matter of identifying its location in the code and then determining if that location is within the bounds of an "enabled" area.

A three-field structure identifies each area. The fields are the source filename, the area's first line of code, and the area's last line of code. To minimize memory requirements, only enabled areas are actually stored; the remaining code is implicitly "disabled." Because this process is more complex than evaluating a simple variable, I use a function call rather than a simple expression for the switch.

Determining Switch Status

This scheme depends on two new pre-defined ANSI C macros: __FILE__ and __LINE__. The preprocessor replaces __FILE__ with the source filename and __LINE__ with an integer line number. These macros allow each switch call to use the same arguments regardless of location. For example,

csw__ison(__FILE__, __LINE__)
(The function prefix, csw, identifies it as part of the "Command-Line Switch" subsystem.) The switch function returns Boolean value TRUE (nonzero — see the typedef in
Listing 1) only if the specified file and line number fall within any of the "enabled" program areas.

The switch function first tests for the most frequently encountered special case — normal execution, in which no areas are enabled. The next test supports a special use of the switch function. By passing a NULL pointer instead of a filename, the programmer can ask if any area is enabled. In the general case, the switch function first strips path information, if any, from the filename parameter and then iteratively compares the resulting filename with the filename of each "enabled" code area. If the filenames match, the switch function will check the line number parameter to determine whether it is within the specified range. Using a line number parameter of zero invokes another special behavior. It asks, in effect, if any line within the specified source file is "enabled."

A simple linear search of "enabled" areas may seem inefficient, but since the number of these areas in use at any one time will be small, search speed isn't a problem.

Parsing The Command Line

The function csw_parse loads the array of structures which csw_ison searches. The csw_parse call should be one of the very first executable statements in main. Like main, csw_parse accesses the command line through the standard argc, argy mechanism:

main( int argc, char *argv[])
{
   /* Declare variables ... */
   csw_parse(argc, argv);
   /* Remaining code ... */
}
The switch functions do not modify the arguments, and thus will not affect the behavior of other code that uses argc and argv. The switch functions identify arguments by key words, allowing them to appear anywhere on the command line. Before processing, each argument is copied into a working buffer and converted to upper case, so that all comparisons will be case insensitive.

Enabled areas are specified by a string of comma-separated fields following the keyword SW. The csw option syntax does not require the line numbers in area specifications; missing line number ranges default to the first and last line of the file. For example:

SW,myprog — sets all switches in the source file MYPROG.C

SW,myprog, 50 — sets switches from line 50 to the end of the file

SW, myprog, 50, 50 — sets switch on line 50 only

The parsing function loops once for each command-line argument. If first copies the argument to a working buffer, converts the argument to uppercase, and then uses sscanf to compare the argument with the csw keyword. The sscanf function will search through each argument string until it finds a match for the format specification. It will then convert the appropriate arguments, returning the count of converted arguments. When no match is found, sscanf returns zero, causing that command-line argument to be ignored.

A Useful Macro

The csw package is most conveniently invoked through a macro. For example, to conditionally execute any statement in your program:

#ifdef NO_CSW
#define CSW_EXEC(x) ((void)0)
#else
#define CSW_EXEC (x) \
   ( csw_ison(_FILE_,_LINE_) ?
(x) : (void)0))
#endif
This macro could also have been written:

#define CSW_EXEC(x) \
   if (csw_ison(_FILE_,_LINE_)) {x}
This version resolves to a statement, however, while the ternary operator version resolves to an expression. It is syntactically better for macros to resolve into expressions. Macros then are more general.

The original debug example can now be written as:

CSW_EXEC( printf("\n TESTING: %s hashes into %d ", key, hash));
The macro hides the repetitive details of the switch function call and automatically removes the switchable statements from the program when NO_CSW is defined. (With many compilers NO_CSW can be defined from the command line (DNO_CSW for Microsoft C), eliminating the need to edit the source file. However, if code size is not a major issue, you should consider leaving the switchable debug statements in the production release — the information they generate can be very valuable in diagnosing problems reported from the field.

Conclusion

Once you create and install this sub-system, you can easily add switchable statements to any program. The code size will be increased only by the statements themselves. If that is a problem, you can remove the statements from compiled code by defining NO_CSW.

While the command-line interface may not be user-friendly, it is more than adequate for programmers who occasionally need to invoke special program features. While I have focused on debugging, there may well be many other applications for switchable statements as well.

Listing 2 (cswitch.c)