C Language

Sidebar 1 - Pulling a "Fast" One

When you cut through all the ivory-tower software engineering stuff, what you really get down to in any C programming showdown is speed. "My loop is faster than your loop!" Who cares if it freezes the system sometimes? So how are you going to survive as a new C programmer if you let yourself be hamstrung by such practices as using array subscripts instead of pointers? Some C programmers would tell you that using array subscripts is like showing up at The Bonneville Salt Flats in a go-cart when you really should bered-lining a methanol-powered, turbo-charged, pointer-driven speed demon. But before accepting any widely held C rules of the road, you may want to look at some actual data.

Figures 6.A, 6.B, and 6.C show three implementations of an upper_case function that loops over a character string, converting lowercase characters to upper case. Figure 6.A's version is about as lean and mean as you can get in C. Figure 6.B's function uses direct pointer manipulation, but in a less tricky loop than A's "speed-demon" version, and Figure 6.C's version uses array subscripting. All three versions produce identical results - most of the time (see the twist at the end of this sidebar). I compiled all three versions with Microsoft C 6.0a, using the large memory model and maximum (/Ox) optimization. For each version, I executed 10,000 calls to upper_case on a string with 100 letters in it. I used the C time function to bracket the test loop and ran all three versions repeatedly on an IBM PS/2 55SX (16 MHz 80386SX). The average times, measured in milliseconds per call, were:

Figure 6.A (speed-demon) 1.2

Figure 6.B (pointers) 1.3

Figure 6.C (subscripts) 1.4

Figure 6.A "Speed-Demon" upper_case Function

 void upper_case( char *str ) {
   while ( *str++ = (char) toupper( *str ) );
 }

Figure 6.B Pointer Implementation of upper_case Function

 void upper_case( char *str ) {
    while ( *str != '\0' ) {
      *str = (char) toupper( *str );
      ++ str;
    }
 }

Figure 6.C Subscript Implementation of upper_case Function

 void upper_case( char str[] ) {
    int i = 0;
    while ( str[ i ] != '\0' ) {
    str[ i ] = (char) toupper( str[ i ] );
    ++ i;
   }
 }

Although the differences aren't exactly dramatic, they're no doubt large enough to fuel a "true believer's" insistence that the code in Figure 6.A is best. If you find yourself on the losing end of such a debate, here's what you do. Bet your opponent you can revise the upper_case function so it still doesn't directly use pointers but runs faster than the speed-demon version. Even offer to spot your opponent a half-millisecond handicap, and bet heavily. Then deliver the code in Figure 6.D. Using Microsoft's strupr library function, upper_case takes only 0.3 milliseconds - a gain that dwarfs the best improvement possible by any hand-coded C iteration over the string. As often happens, changing a program's approach yields greater performance improvements than diddling with code. Armed with this strategy for writing C programs, you'll be able to pull off additional "fast ones."

Figure 6.D Library Function Implementation of upper_case Function

 void upper_case( char *str ) {
    strupr( str );
 }

A Final Twist

When I ran the original benchmarks for this sidebar, I used the Microsoft C /qc (quick compile) option. Subsequently, I repeated the tests without the /qc option, and the code in Figure 6.A no longer worked. A call to Microsoft revealed another C pitfall: There's no standard order for evaluating the left and right sides of an assignment expression. With the /qc option, MS-C follows the intuitive approach and evaluates the right side (i.e., (char) toupper( *str )) before the left side (*str++). This approach produces the expected results. But without /qc, MS-C evaluates the left side first, incrementing the address in str before it's used in the right-hand expression. This causes each invocation of upper_case to chop the leading, non-null character from the string, eventually wiping out the string altogether. This discovery inspired a new guideline for avoiding another C pitfall: Don't use ++ or - in assignments. This rule isn't limited to expressions involving pointers. The expression

 x = (i) + (i++)

is also ambiguous because the compiler may evaluate the first i before or after the post-increment of i occurs.

Sidebar 2 - C Coding Suggestions

For non-array "output" or "input/output" parameters, use local variables instead of dereferenced parameters in function calculations.
Use array notation instead of pointers and dereferencing when you're working with arrays.
When working with pointers in assignment statements, double-check that you're using the right level of indirection.
Unless you're positive a pointer has been initialized, check it for NULL before using it.
Use a new_string function to return new strings from functions.
Always check for a NULL return value after calling malloc.
Use the allocate macro to prevent memory "leakage."
Always initialize pointers in their definitions.
Use typedef and PTR, contents_of, and address_of macros to improve program readability.
Avoid popular, but tricky, C idioms for business application programming.
Don't use ++ or - in assignments.

by BrainBellupdated
Advertisement: