Previous | Table of Contents | Next |
All Functions Normal
Most C programmers have adopted the good programming practice: Always declare a function prototype at the beginning of any file in which you use the function.
This practice prevents accidentally treating a functions return value as int (the type the compiler assumes when no prototype is declared) when it is some other type. It also lets the compiler check that the proper type of arguments are specified when the function is used. For standard C library functions this principle implies the rule: Always include the header file for any standard library function you use. Following this example for your own functions, you should also define a header file with function prototypes for every file that has global functions that may be referenced in other files. Then you can include these user-defined header files as a simple and foolproof way to declare function prototypes for all shared functions.
Another practice thats available with recent C compilers is to declare formal parameters to functions as const, if they should not be changed. C passes function arguments by value, so you can never really change the variable thats passed by the calling program anyway. For example, in the following code, the value of arg1 is not changed in main, even though the corresponding parameter parm1 is changed in the function. A copy of arg1, stored in a temporary location, is whats changed by the function.
main(...) { int arg1; f( arg1 ); ... } void f( int parm1 ) { parm1 = 10; return;
So why bother with declaring function arguments as const, as in the following example?
void f( const int parm1 );
The advantage of this type of declaration is that youll be warned if you inadvertantly try to modify the argument, thinking the new value will be reflected in the calling function. This type of error is easily made by programmers used to languages, such as Pascal and COBOL, that let you pass arguments by reference and modify the value in the calling program by changing a parameter. Declaring a parameter as const also makes clear the parameter is meant as an input-only parameter.
Dont be lured into avoiding a const parameter specification because of the common C practice of using input-only parameters as if they were local variables. Although Cs pass-by-value handling of arguments allows this techinique, the only potential advantages are saving a trivial amount of automatic storage (for a local variable) and execution time (for automatic storage allocation and an assignment).
What you give up is the added protection the compiler can provide against improper use of the input-only parameter.
In Chapter 6, I suggested you use array notation, such as x[], instead of pointer notation, such as *x, for clarity. This practice has an added benefit with array parameters because a function declaration like int strlen( const char str[] ); specifies that no element of the array argument can be changed. And, since array names are not names of pointer variables, no statement in the function can attempt to modify str itself. With pointer notation, you can also specify that no modifications be allowed via indirect references that use a pointer parameter:
int strlen( const char * str );
But this doesnt prevent inadvertant changes to the copy of the pointer itself:
++ str;
Only the const keyword, used with array notation, specifies that both the array address and its contents must be treated as read-only within the function.
Do What I Mean, Not What I Say
Like a typical house cat, C programs sometimes seem to ignore direct commands. As an example, the following code appears to clearly say when its time to leave.
if ( x < o ) { printf( "Invalid value.\n" ); exit; }
But no matter how negative x is, this program continues. In C, a function name without the argument list parentheses is simply evaluated as the functions address. Its perfectly legal, yet the function isnt actually invoked. Be sure youve coded parentheses after all function invocations.
Gently Down the Stream
C stream I/O is the model of simplicity, yet it has some tricky areas, too. A defensive programmer might code
c = getchar(); if ( errno != 0 ) { /* handle error */ }
But this code may report false errors because most of Cs library functions set the library-defined variable errno to a non-zero value only when an error occurs.
Otherwise, they leave errno unchanged. Simply initializing errno before the call isnt an adequate solution, because a C library function may set errno,even if no error exists! Thus, the only safe approach to using errno is shown in the following example of using fopen
errno = 0; fileptr = fopen( ... ); if ( fileptr == NULL ) { /* An error occurred in fopen() | Now it's valid to examine errno */ if ( errno != 0 ) { /* handle error */ } }
The rule for using errno is: Set errno to 0 before a function call, and use errno only after a function returns a value indicating the function failed.
New Dimensions
Programmers moving to C from some other languages can be tripped up when they use multidimensional arrays. In many languages, a subscripted reference to a two-dimensional array has a form like x[ i, j ]. C is different, and a reference like the second statement below
int x[10][10]; y = x[ ++i, ++j ];
does not indicate that two subscripts are used in the reference to array x. Instead, ++i, ++j is a comma-separated sequence of expressions, and the expression value is the value of the last sub-expression in the sequence (i.e., j, after j is incremented). C doesnt actually have true multi-dimensional arrays. Recall that for the most part, C array notation is really a variation on pointer notation, and that a[i] is equivalent to *(a+i). To get the effect of a multi-dimensional array in C, you declare arrays of arrays (i.e., two levels of pointer-based addressing). The notation a[i][j] means *((*(a+i))+j). In the incorrect example above, the value of x[ ++i, +j&43;# ] is the same as *(x+(++j)), which is an address, not an integer. In C, always use one pair of [] for each level of array subscripting.
Previous | Table of Contents | Next |