Add Book to My BookshelfPurchase This Book Online

Appendix A - Significant Changes in ANSI C

UNIX Systems Programming for SVR4
David A. Curry
 Copyright © 1996 O'Reilly & Associates, Inc.

Functions
ANSI C has also made two significant changes to the way functions are declared and called.
Function Prototypes
The most visible change in ANSI C is the introduction of function prototypes, borrowed from C++. With function prototypes, the number and type of a function's parameters are specified when the function is declared. This allows the compiler to perform type checking, and also to avoid unnecessary type promotions.
We have used function prototypes throughout this book. For example:
    FILE *fopen(char *filename, char *mode);
This is the most explicit of the prototype syntaxes. It is also possible to leave out the variable names in the prototype, e.g.,
    FILE *fopen(char *, char *);
However, the variable names help in remembering what parameter goes where; the second form provides no clue in this regard. And, of course, the old pre-ANSI syntax is still valid:
    FILE *fopen();
However, in this case, the compiler is not able to perform type checking.
Function definition may follow either the most explicit of the prototype syntaxes,
    FILE *
    fopen(char *filename, char *mode)
    {
       ·
       ·
       ·
    }
or it may follow the old pre-ANSI syntax:
    FILE *
    fopen(filename, mode)
    char *filename, *mode;
    {
       ·
       ·
       ·
     }
Note, however, that the type of each parameter must be specified explicitly, even if two consecutive parameters have the same type. In other words,
    FILE *fopen(char *filename, char *mode);
is correct, but
    FILE *fopen(char *filename, *mode);  
is not.
Functions with a variable number of arguments are handled with a trailing “...”. This means that there may be zero or more parameters after this point. For example, the prototype for the fprintf function looks like:
    int fprintf(FILE *, const char *, ...);
Note that this syntax requires that the “...” be last in the list.
Finally, functions with no parameters are now declared using the void type:
    int getpid(void);
This allows the compiler to make sure that no parameters are passed to the function when it is compiled.
Handling prototypes in non-ANSI environments
Even though you may be using an ANSI C compiler, it is quite likely that the code you are writing will have to be compiled on a system that does not have an ANSI compiler. Rather than avoiding the use of function prototypes altogether, there are a few approaches you can take.
The simplest approach simply has two declarations for every function:
    #ifdef _ _STDC_ _
    int fact(int);
    #else
    int fact();
    #endif
    #ifdef _ _STDC_ _
    int fact(int n)
    #else
    int fact(n)
    int n;
    #endif
    {
       ·
       ·
       ·
     }
Unfortunately, this is rather ugly. Another possibility is to do the above for the declarations, but use old-style definitions:
    #ifdef _ _STDC_ _
    int fact(int);
    #else
    int fact();
    #endif
    int fact(n)
    int n;
    {
       ·
       ·
       ·
     }
This is less ugly, but still requires declaring the function twice, leaving a potential for error.
A more elegant solution, one that you will see used often, is to define a macro, usually called _P or _proto, that handles the prototypes, and then use old-style definitions:
    #ifdef _ _STDC_ _
    #define _P(args)    args
    #else
    #define _P(args)    ()
    #endif
    int fact _P((int));
    int fact(n)
    int n;
    {
       ·
       ·
       ·
     }
When _ _STDC_ _ is defined, the prototype expands to
    int fact (int);
while when _ _STDC_ _ is not defined, it expands to
    int fact ();
Widened Types
In K&R C, the compiler had no way to type-check function parameters, and would promote all arguments of types smaller than int to int, and all arguments of type float to double. Since most compilers at the time performed all floating-point arithmetic in double precision anyway, this wasn't usually a problem.
ANSI C still promotes function parameters to their widened types when a function is called. However, inside the function, the widened types are converted back to their original, narrower sizes. This can cause some serious problems with carelessly-written pre-ANSI code.
One of the most common errors is to assume that floats are really doubles. For example:
    foo(f)
    float f;
    {
        bar(&f);
    }
    bar(d)
    double *d;
    {
       ·
       ·
       ·
     }
The problem here is that in pre-ANSI C, f never really was a float. It was declared as one, but the compiler treated it as a double. So in bar, where we assumed a pointer to a double, you could get away with it, because that's how things really worked.
In ANSI C, you will not get a warning from the compiler about this, because, being pre-ANSI C, there are no function prototypes (which serves to prove that function prototypes are a good thing). But, when you try to execute your program, bar will fail in any one of a number of different ways trying to use *d as if it were actually a double.
To avoid this problem, when writing code to be used both with and without function prototypes, use only widened types—no char or short (use int), and no float (use double). Pointers to any of the types (widened or unwidened) are okay.

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback