C Language

Letting the Cat Out of the Bag

Although C has plenty of built-in traps, C's design is probably less to blame for pointer problems than C programmers' fascination with clever coding. Consider the cat function in Figure 6.14. This function places the concatenation of two null-terminated strings (passed as parameters str1 and str2) in the result string. I wrote cat using a typical C idiom. The "beauty" of this style lies in how much work gets done in the two short while expressions. Each execution of

 while(*result++ = *str1++)

works like this:

1. Get the value of str1 (an address).
2. Increment the value of str1 (bump the address one byte).
3. Get the character stored at the address obtained in step 1 (before incrementing the address).
4. Get the value of result (an address).
5. Increment the value of result (bump the address one byte).
6. Store the character obtained in step 3 at the address obtained in step 4 (before incrementing the address).
7. Compare the binary value of the character stored in step 6. If it's zero, quit the while loop; otherwise, repeat steps 1 through 7 with the incremented address values of str1 and result.

Figure 6.14 cat Function

 void cat fees( char * result, const char * str1, const char * str2 ) {
    /* Concatenate str1 and str2 and return in result */
    while(*result++ = *str1++);
    while(*result++ = *str2++);

When the first while loop is completed, result contains the address one beyond the terminating null of the target string. The -result statement shifts this address back one, so the second while loop repeats the process just described, starting at the byte just after the last non-null character in the target.

If you're an inexperienced C programmer, this code probably looks odd to you. You may not even be sure it works reliably. But if you aspire to be a competent C programmer, much of the advice you'll receive will try to help you master C's concentrated syntax so that you will know how this code works and that it does do what I said. Unfortunately, too many C programmers concentrate so much on coding details (such as when the post-increment operator takes effect and the relative precedence of the * and ++ operators) that they deliver tight while loops but miss larger problems. Such misplaced attention can lead to functions such as cat that are fast, but explosive.

Figure 6.15a shows a sample call to cat that prints "Hello world!" - just what you'd expect. Figure 6.15b shows another call to cat that freezes your PC and requires a reboot. Since the second example passes string b as both the target string (result parameter) and the second source string (str2 parameter), the second while loop in cat chases b to infinity. On every iteration, we advance the pointer one more byte toward the end of str2 (which is b), but by adding a byte to the tail of result (which is also b), we make the end of str2 just one byte farther away.

Figure 6.15a A Successful Call of cat

 char a[20] = "Hello ";
 char b[20] = "world!";
 char x[20];
 cat(x, a, b);
 printf("%s\n", x); /* Prints:  Hello world!  */

Figure 6.15b An Unsuccessful Call of cat

 char a[20] = "Hello ";
 char b[20] = "world!";
 char x[20];
 cat(b, a, b);   /* Infinite loop! */
 printf("%s\n", b);

Similar problems can occur in RPG, COBOL, and any other language that passes addresses for procedure arguments. But because RPG and COBOL programmers don't have to concentrate so much on low-level coding details (or maybe because RPG and COBOL programmers don't so readily dare to "boldly go where no one has gone before"), they don't seem to write as much self-destructing code as C programmers.

That brings me to my last suggestion in this chapter for avoiding C's pitfalls: avoid popular, but tricky, C idioms for business application programming. Instead, use code that is readily understood and easily checked for validity.