|
9.1.6 K&R Compilers
K&R C is the name now used to describe the original C language specified
by Brian Kernighan and Dennis Ritchie (hence, `K&R'). I have
yet to see a C compiler that doesn't support code written in the K&R
style, yet it has fallen very much into disuse in favor of the newer
ANSI C standard. Although it is increasingly common for vendors to
unbundle their ANSI C compiler, the GCC
project(11) is available for all of the architectures I have ever
used.
There are four differences between the two C standards:
-
ANSI C expects full type specification in function prototypes, such
as you might supply in a library header file:
|
extern int functionname (const char *parameter1, size_t parameter 2);
|
The nearest equivalent in K&R style C is a forward declaration, which
allows you to use a function before its corresponding definition:
|
extern int functionname ();
|
As you can imagine, K&R has very bad type safety, and does not perform
any checks that only function arguments of the correct type are used.
-
The function headers of each function definition are written
differently. Where you might see the following written in ANSI C:
|
int
functionname (const char *parameter1, size_t parameter2)
{
...
}
|
K&R expects the parameter type declarations separately, like this:
|
int
functionname (parameter1, parameter2)
const char *parameter1;
size_t parameter2;
{
...
}
|
-
There is no concept of an untyped pointer in K&R C. Where you might be
used to seeing `void *' pointers in ANSI code, you are forced
to overload the meaning of `char *' for K&R compilers.
-
Variadic functions are handled with a different API in K&R C,
imported with `#include <varargs.h>'. A K&R variadic function
definition looks like this:
|
int
functionname (va_alist)
va_dcl
{
va_list ap;
char *arg;
va_start (ap);
...
arg = va_arg (ap, char *);
...
va_end (ap);
return arg ? strlen (arg) : 0;
}
|
ANSI C provides a similar API, imported with `#include
<stdarg.h>', though it cannot express a variadic function with no named
arguments such as the one above. In practice, this isn't a problem
since you always need at least one parameter, either to specify the
total number of arguments somehow, or else to mark the end of the
argument list. An ANSI variadic function definition looks like
this:
|
int
functionname (char *format, ...)
{
va_list ap;
char *arg;
va_start (ap, format);
...
arg = va_arg (ap, char *);
...
va_end (ap);
return format ? strlen (format) : 0;
}
|
Except in very rare cases where you are writing a low level project
(GCC for example), you probably don't need to worry about K&R
compilers too much. However, supporting them can be very easy, and if
you are so inclined, can be handled either by employing the
ansi2knr program supplied with Automake, or by careful use of
the preprocessor.
Using ansi2knr in your project is described in some detail in
section `Automatic de-ANSI-fication' in The Automake Manual, but
boils down to the following:
-
Add this macro to your `configure.in' file:
-
Rewrite the contents of `LIBOBJS' and/or `LTLIBOBJS' in
the following fashion:
|
# This is necessary so that .o files in LIBOBJS are also built via
# the ANSI2KNR-filtering rules.
Xsed='sed -e "s/^X//"'
LIBOBJS=`echo X"$LIBOBJS"|\
[$Xsed -e 's/\.[^.]* /.\$U& /g;s/\.[^.]*$/.\$U&/']`
|
Personally, I dislike this method, since every source file is filtered
and rewritten with ANSI function prototypes and declarations
converted to K&R style adding a fair overhead in additional files in
your build tree, and in compilation time. This would be reasonable were
the abstraction sufficient to allow you to forget about K&R entirely,
but ansi2knr is a simple program, and does not address any of
the other differences between compilers that I raised above, and it
cannot handle macros in your function prototypes of definitions. If you
decide to use ansi2knr in your project, you must make the
decision before you write any code, and be aware of its limitations as
you develop.
For my own projects, I prefer to use a set of preprocessor macros along
with a few stylistic conventions so that all of the differences between
K&R and ANSI compilers are actually addressed, and so that the
unfortunate few who have no access to an ANSI compiler (and who
cannot use GCC for some reason) needn't suffer the overheads of
ansi2knr .
The four differences in style listed at the beginning of this subsection
are addressed as follows:
-
The function prototype argument lists are declared inside a
PARAMS
macro invocation so that K&R compilers will still be able to compile the
source tree. PARAMS removes ANSI argument lists from
function prototypes for K&R compilers. Some developers
continue to use __P for this purpose, but strictly speaking,
macros starting with `_' (and especially `__') are reserved
for the compiler and the system headers, so using `PARAMS', as
follows, is safer:
|
#if __STDC__
# ifndef NOPROTOS
# define PARAMS(args) args
# endif
#endif
#ifndef PARAMS
# define PARAMS(args) ()
#endif
|
This macro is then used for all function declarations like this:
|
extern int functionname PARAMS((const char *parameter));
|
-
With the
PARAMS macro is used for all function declarations,
ANSI compilers are given all the type information they require to
do full compile time type checking. The function definitions
proper must then be declared in K&R style so that K&R compilers don't
choke on ANSI syntax. There is a small amount of overhead in
writing code this way, however: The ANSI compile time type
checking can only work in conjunction with K&R function definitions if
it first sees an ANSI function prototype. This forces you to
develop the good habit of prototyping every single function in
your project. Even the static ones.
-
The easiest way to work around the lack of
void * pointers, is to
define a new type that is conditionally set to void * for
ANSI compilers, or char * for K&R compilers. You
should add the following to a common header file:
|
#if __STDC__
typedef void *void_ptr;
#else /* !__STDC__ */
typedef char *void_ptr;
#endif /* __STDC__ */
|
-
The difference between the two variadic function APIs pose a
stickier problem, and the solution is ugly. But it does work.
First you must check for the headers in `configure.in':
|
AC_CHECK_HEADERS(stdarg.h varargs.h, break)
|
Having done this, add the following code to a common header file:
|
#if HAVE_STDARG_H
# include <stdarg.h>
# define VA_START(a, f) va_start(a, f)
#else
# if HAVE_VARARGS_H
# include <varargs.h>
# define VA_START(a, f) va_start(a)
# endif
#endif
#ifndef VA_START
error no variadic api
#endif
|
You must now supply each variadic function with both a K&R and an
ANSI definition, like this:
|
int
#if HAVE_STDARG_H
functionname (const char *format, ...)
#else
functionname (format, va_alist)
const char *format;
va_dcl
#endif
{
va_alist ap;
char *arg;
VA_START (ap, format);
...
arg = va_arg (ap, char *);
...
va_end (ap);
return arg : strlen (arg) ? 0;
}
|
|