ANSI C prototypes

Chris Torek chris at mimsy.umd.edu
Tue Nov 6 21:16:40 AEST 1990


>In article <1005 at christopher-robin.cs.bham.ac.uk> ptf at cs.bham.ac.uk
>(Paul Flinders <FlindersPT>) writes:
>>[my foo.h contains
>> 	extern void ddprintf(const char *fmt, ...);
>> and my foo.c contains
>> 	void ddprintf(va_alist)
>> 	va_dcl;
>> and so I get an error].  The only solution that I've thought of is to
>>define some preprocessor token in foo.c _before_ the include of foo.h
>>and then #ifdef the extern declaration out.

In article <BPaVR3w161w at phoenix.com> stanley at phoenix.com (John Stanley)
provides a correct answer to a completely different question:
>   1) Use a VAX. VMS C does not (at least, did not) care about the extern
>missing from extern declarations. This means the same include that
>defines a global int i may be included in all files.

This refers to object declarations / definitions, as opposed to
function declarations / definitions.  VAX/VMS C uses the same model
that most modern systems use, in which

	int foo;

means: `There is an object called foo which is declared the same way
in some other source file(s), possibly but not necessarily with ``extern''
added on the front.'  (This is also known as `the common model' since
it implements FORTRAN's named COMMON blocks.)  Other systems, however,
treat the above as both a declaration and a definition: `There is
an object called foo.  This is it.  This is the only one.'  On these
systems writing `extern int foo;' means:  `There is an object called
foo.  This is not it.  Get me that object.'  (This is also known as
the `def/ref' model: a declaration without `extern' is a DEFinition,
while one with `extern' is a REFerence, and there must be exactly one
definition for everything.)

The def/ref model is all that is required of a C system.  Some find
the common model more convenient; some find it more error-prone; since
FORTRAN virtually requires it, most systems have it, but sometimes in
a very limited form.

John Stanley goes on to provide an incorrect answer (but one which, if
interpreted in the context of the question answered above, becomes correct):
>   2) Do what you thought of. It is pretty common, in my experience, that
>the main routine has a #define MAIN, with the included files having:
>
>	#ifndef MAIN
>	#define EXTERN
>	#else
>	#define EXTERN extern
>	#endif
>
>This way, all global variables get defined in the main routine, and
>declared in the rest.

This works fine for variables, but is not correct for function prototypes
(more on this in a moment).

>You probably could just #define extern to nothing,
>but that would screw up any externs in any files follwing that one.

Well, you can then `#undef extern'.  A more likely problem is that
many compilers refuse to allow `#define'ing anything that resembles
a keyword (incorrectly, by ANSI X3.159-1989, since keywords do not
exist during the preprocessing phase of the translation).

Okay, so what is wrong with:

	extern void ddprintf(const char *fmt, ...);

followed by

	void ddprintf(va_alist) va_dcl {

?  The answer is in fact contained in the FAQ answers, but here it
is again.  EXCEPT IN RESTRICTED CIRCUMSTANCES, PROTOTYPE DECLARATIONS
ARE NOT COMPATIBLE WITH OLD-STYLE DEFINITIONS.

Note that both

	extern void ddprintf(const char *fmt, ...);

and

	void ddprintf(const char *fmt, ...);

are declarations.  The word `extern' is ignored when declaring
functions.  The difference between a function declaration and a
function definition is that a declaration ends with a balancing close
parenthesis followed by a semicolon, while a definition ends with a
balancing close parenthesis followed by an opening `{'.  It is
incorrect to put `extern' in front of a definition: `extern' is *not*
ignored when DEFINING functions, only when DECLARING them.

A prototype declaration tells the compiler:  `This name is the name of
a function.  It has a fixed number of arguments, and each of these has
a fixed type.  The function returns a fixed type.  Remember all of
these facts.'  If the declaration contains a `, ...' before the closing
parenthesis, this changes to:  `This name is the name of a function.
It has a variable number of arguments, preceded by a fixed number of
arguments.  Each of the latter has a fixed type.  The function returns
a fixed type.  Remember all of these facts.'

When the compiler discovers a prototype DEFINITION, it treats that as
another declaration (or the first declaration, if there have been no
other declarations for the function) and makes sure the arguments
match in number and types, and that the return type matches; it then
compiles code for the function body.

When the compiler discovers an old-style declaration or definition,
however, this tells the compiler:  `This name is the name of a
function.  It may or may not have some arguments.  The arguments,
if any, are not variable.  The function returns a fixed type.  Remember
all of these facts.'

The latter can sometimes, but by no means always, be reconciled with
a prototype declaration that gave the same name and return type, but
also gave the number and types of the arguments.  It is not too difficult
to define exactly when this can be done, although the result can be
surprising.

A: The arguments must be fixed, not variable.  The ANSI standard
   permits compilers to use different calling sequences for fixed-
   and variable-argument functions, and therefore requires the
   programmer to announce variable-argument functions `in advance',
   as it were, using a prototype.  All others are assumed to be
   fixed-arguments.

B: None of the arguments in the prototype being reconciled against
   the definition may have a type which differs from its image under
   promotion.

What B really means is that no argument can have type `char', `short',
or `float' or any of their signed or unsigned variants.  In other
words, although

	int putc(int c, FILE *f);
	int putc(c, f) int c; FILE *f; { ... }	/* ok */

is legal, the following is not:

	int put1(char c, FILE *f);
	int put1(c, f) char c; FILE *f; { ... }	/* bad */

Since the image of `char' under promotion is `int', this fails rule
B.  Likewise,

	float mul(float, float);
	float mul(x, y) float x, y; { ... }	/* bad */

is illegal, because the image of `float' under promotion is `double'.

The reason for restriction B has to do with backwards compatibility
(being able to run the mounds of code that use old-style definitions)
without requiring systems to promote *all* arguments (being able to
pass `float's around without turning them into `double's, etc).  Any
C system can *allow* the codes marked `bad' to work, but no C system
is *required* to do so.

So (after 162 lines), the answer is: declare your function as

	/* with optional `extern' included here */
	extern void ddprintf(const char *fmt, ...);

and define it as

	void ddprintf(const char *fmt, ...) {

and any ANSI C system is obliged to compile it correctly.  Do anything
else and all bets are off.  Since older C compilers do not recognize the
`, ...' syntax, you may have to write:

	#ifdef HAVE_PROTOTYPE_DECLARATIONS
	void ddprintf(constchar *fmt, ...);
	#else
	void ddprintf();
	#endif
and
	#ifdef HAVE_PROTOTYPE_DEFINITIONS
	void ddprintf(const char *fmt, ...) {
	#else
	void ddprintf(va_alist) va_dcl { char *fmt;
	#endif
		va_alist ap;
	#ifdef HAVE_PROTYTYPE_DEFINITIONS
		va_start(ap, fmt);
	#else
		va_start(ap); fmt = va_arg(ap, char *);
	#endif
		...
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 405 2750)
Domain:	chris at cs.umd.edu	Path:	uunet!mimsy!chris



More information about the Comp.lang.c mailing list