A Little C Primer/Common Programming Problems in C

There are a number of common programming pitfalls in C that trap even experienced programmers:

1: Confusing "=" (assignment operator) with "==" (equality operator). For example:

   if ( x = 1 ){ /* wrong! */
   }
   ...
   if ( x == 1 ){ /* good. */
   }

and

   for ( x == 1; ... /* wrong! */
   ...
   for ( x = 1; ... /* good. */

2: Confusing precedence of operations in expressions. When in doubt, use parentheses to enforce precedence. It never hurts to use a few extra parenthesis.

3: Confusing structure-member operators. If "struct_val" is a structure and "struct_ptr" is a pointer to a structure, then:

   struct_val->myname

—is wrong and so is:

   struct_ptr.myname

4: Using incorrect formatting codes for "printf()" and "scanf()". Using a "%f" to print an "int", for example, can lead to bizarre output.

5: Remember that the actual base index of an array is 0, and the final index is 1 less than the declared size. For example:

   int data[20];
   ...
   for ( x = 1; x <= 20; ++x )
   {
     printf( "%d\n", data[x] );
   }

—will give invalid results when "x" is 20. Since C does not do bounds

checking, this one might be hard to catch.

6: Muddling syntax for multidimensional arrays. If:

   data[10][10]

—is a two-dimensional array, then:

   data[2][7]

—will select an element in that array. However:

   data[ 2, 7 ]

—will give invalid results but not be flagged as an error by C.

7: Confusing strings and character constants. The following is a string:

   "Y"

—as opposed to the character constant:

   'Y'

This can cause troubles in comparisons of strings against character constants.

8: Forgetting that strings end in a null character ('\0'). This means that a string will always be one character bigger than the text it stores. It can also cause trouble if a string is being created on a character-by-character basis, and the program doesn't tack the null character onto the end of it.

9: Failing to allocate enough memory for a string—or, if pointers are declared, to allocate any memory for it at all.

10: Failing to check return values from library functions. Most library functions return an error code; while it may not be desirable to check every invocation of "printf()", be careful not to ignore error codes in critical operations.

Of course, forgetting to store the value returned by a function when that's the only way to get the value out of it is a bonehead move, but people do things like that every now and then.

11: Having duplicate library-function names. The compiler will not always catch such bugs.

12: Forgetting to specify header files for library functions.

13: Specifying variables as parameters to functions when pointers are supposed to be specified, and the reverse. If the function returns a value through a parameter, that means it must be specified as a pointer:

   myfunc( &myvar );

The following will not do the job:

   myfunc( myvar );

Remember that a function may require a pointer as a parameter even if it doesn't return a value, though as a rule this is not a good programming practice.

14: Getting mixed up when using nested "if" and "else" statements. The best way to avoid problems with this is to always use brackets. Avoiding complicated "if" constructs is also a good idea; use "switch" if there's any choice in the matter. Using "switch" is also useful even for simple "if" statements, since it makes it easier to expand the construct if that is necessary.

15: Forgetting semicolons—though the compiler usually catches this—or adding one where it isn't supposed to be—which it usually doesn't. For example:

   for( x = 1; x < 10; ++x );
   {
      printf( "%d\n", x )
   }

—never prints anything.

16: Forgetting "break" statements in "switch" constructs. As commented earlier, doing so will simply cause execution to flow from one clause of the "switch" to the next.

17: Careless mixing and misuse of signed and unsigned values, or of different data types. This can lead to some insanely subtle bugs. One particular problem to watch out for is declaring single character variables as "unsigned char". Many I/O functions will expect values of "unsigned int" and fail to properly flag EOF. It is recommended to cast function arguments to the proper type even if it appears that type conversion will take care of it on its own.

18: Confusion of variable names. To ensure portability of code, it is recommended that the first six characters of such identifiers be unique.

19: In general, excessively tricky and clever code. Programs are nasty beasts and even if it works, it will have to be modified and even ported to different languages. Maintain a clean structure and do the simple straightforward thing, unless it imposes an unacceptable penalty.