Previous | Table of Contents | Next |
The switch statement may be a honey of an operation for writing small, fast device drivers, but it can be a sticky mess for typical business programming. Code that intentionally jumps into the middle of a sequence of operations is extremely tricky to maintain, and you should avoid it except in low-level systems programs. Fortunately, there's a very simple rule for business (and most other) programmers to follow: Never use the C switch statement.
This stricture is not at all burdensome. As I indicated, most instances of C's switch should have a break after every case. For such multiway conditions, you can simply use an "else if" structure instead:
if (color == 1) { printf("red\n"); } else if (color == 2) { printf("blue\n"); } else { printf("Invalid color\n"); }
Better yet, you can use the EQ macro described in the last installment and the macros presented above (IF, THEN, ELSEIF, ELSE, and ENDIF) to code the tests as in Figure 3.1. This solution has a compact, table-oriented layout and avoids the hazards of raw C.
Figure 3.1 - Coding an ELSEIF in C with Macros
IF color EQ 1 THEN printf("red\n"); ELSEIF color EQ 2 THEN printf("blue\n"); ELSE printf("Invalid color\n"); ENDIF
Most programmers know that "comments lie," which is why high-level languages should let you directly express what your program does rather than force you to comment unclear code. C programmers will find that comments can also make their code lie! Read the fragment in Figure 3.2 carefully. A comment warns anybody reading the code about an important condition that changes at this point in the program flow. The comment tells us that here is where the variable prv_opcode changes from the previous opcode to the current opcode. A look at the C code seems to verify that the comment doesn't lie. But the C code (or what looks like C code) itself lies. The statement
strcpy(prv_opcode, op_code);
doesn't copy op_code to prv_opcode. It doesn't do anything it's part of a multiline comment, not executable code. The comment ends with the */ on the last line in the figure, making all of Figure 3.2 one long comment.
Figure 3.2 - Sample C Code
/* IMPORTANT NOTE: prv_opcode is set here, after handling vendor-specific translations and blank opcodes. After this section, opcode may be modified. You should _not_ test prv_opcode after this point because it now holds the current opcode.
strcpy(prv_opcode, opcode); setnull(stk_opcode); setnull(op_symbol); setnull(op_suffix); op_is_ctlop = FALSE; /* Control opcodes are ones that cause indentation: BEGSR, IFxx, DO, DOUxx, and DOWxx.*/
[Note call-outs see magazine version, p. 114, Sept. 91]
C uses /* and */ to delimit comments. C also implicitly continues open comments across multiple lines until the ending */ is encountered. This makes it easy to have "runaway" comments that encompass what's intended as executable code. Unintentionally commented-out code, especially if it's initialization code, can cause mysterious program behavior. You see the program fail, you look at the code, and it "can't do that!" Only when, on your tenth look, you finally catch that the comment a page up has no closing */ do you unfold the mystery.
No foolproof way exists to avoid runaway C comments. (Newer languages such as Ada let you prevent this problem by using to start comments that end at the end of the line.) Two rules can help: Place the opening /* and closing */ for comments on lines by themselves, and use a vertical bar to begin each line of comment text. For example,
/* |Comment lines |are here */
This practice avoids the most common cause of a missing */ editing the last line of a comment and accidentally deleting the */ at the end of the line. It's also easier to check visually for matching comment delimiters when they appear at the same indentation level in the source. In addition, some C "lint" utilities can catch occurrences of /* inside a comment, which usually indicates a missing */.
If C's pitfalls somewhat tarnish its image, remember that your C programs can still shine if you polish your programming techniques. The most important thing you can do to improve C programs is take "C-riously" the dangers of writing C code in the traditional (some would say, "C-eat of the pants") manner. Don't try to make your C code "do a lot with just a few statements," and don't hesitate to use source macros to lift yourself to a higher, safer language level than C primitives. Sure, your code will look foreign to old-style C programmers, but your running programs will look a lot better to your end users.
* * * * *
Answer to Chapter 2's puzzle: Figure 3.3 shows corrections to the three errors in the original code. The structure definition was missing the final semicolon (A), causing the compiler to treat the definition as the return type of the main function. The for loop had an extra semicolon immediately after the parentheses (B), so the body of the for loop was the null statement instead of the code within the braces. And the printf format string was missing (C) a newline character (\n), causing the results to be printed in a continuous stream, rather than one item per line.
Figure 3.3 - Corrected C from Chapter 2
struct part { int part_number; char description[30]; }; main() { int i; /* Array of part numbers and descriptions */ struct part part_table[100] = { {011, "Wrench" }, {067, "Screwdriver" }, {137, "Hammer" }, {260, "Pliers" }, /*etc. */ {0, "sentinel" } }; for (i=0; i<100; i++) /* Print the list of parts */ { if (part_table[i].part_number == 0) break; printf("%i %s\n", part_table[i].part_number, part_table[i].description); } }
Previous | Table of Contents | Next |