Back: Fallback Function Implementations
Forward: A Simple Shell Builders Library
 
FastBack:
Up: GNU Autotools in Practice
FastForward: A Simple Shell Builders Library
Top: Autoconf, Automake, and Libtool
Contents: Table of Contents
Index: Index
About: About this document

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:

  1. 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.

  2. 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;
    {
      ...
    }
    

  3. 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.

  4. 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:

     
    AM_C_PROTOTYPES
    

  • 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:

  1. 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));
    

  2. 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.

  3. 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__ */
    

  4. 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;
    }
    


This document was generated by Gary V. Vaughan on February, 8 2006 using texi2html