From: hendrik@vedge.UUCP (Hendrik Boom)
Newsgroups: comp.std.c
Subject: Re: mixing prototyping and non-prototyping
Date: 19 Aug 92 15:55:06 GMT
Organization: Visual Edge Software, St. Laurent, Quebec

spuler@coral.cs.jcu.edu.au (David Spuler) writes:
: 
: What are the rules regarding mixing old-style and new-style functions in C
: programs?  Specifically, what mixtures must a compiler support,and which can
: be legally allowed to bomb?

Here's my standard handout on this subject:

More light and heat on prototypes

A close reading of the ANSI C standard reveals the following 
convoluted relationships between prototypes, old and new style
function definitions, and function calls.  Please comment,
especially if you detect errors in my interpretation.

First, some terminology.

A function definition is the thing that contains
the executable code for the function. A function declaration
only announces that a particular name is to be used
as a name for the function, and does not contain the
executable code for the function.  Any and all of a function
definition, declaration, and call may contain information
about the argument types, with varying significane.

We call them parameters in a declaration or definition,
and arguments in a call.



Case: No prototypes at all.
---------------------------

This is how old-stype K&R C worked.

The interpretation by a compiler of parameters in a function
definition and its interpretation of arguments in a call
were independent.  To lesen the pain somewhat, conversion rules
were defined that made things slightly more likely to match up.

ANSI defines the old-stype functions to worl as follows.

The old-style rules apply whenever a function has neither
been declared with prototype syntax
nor been defined with new-style syntax.

The function call applies a set of
standard conversions, and the function definition expects
the standard conversions to have been applied.

The standard conversions at the point of call are defined
independent of the actual parameter types expected by the
function definition.

To make this work at least some of the time,
a function defined using old-style syntax,
will expect its parameters to have been converted
using the standard argument promotion rules.

A function with a single real argument, such as
	f(r)real r;{    }
will in fact expect a double.
If you pass a double to it, the double gets passed as is.
If you pass a real to it, the real will be converted to
double at the point of call, and the double will be
passed instead.
If you pass an int, no conversion takes place. This will
probably not work.
If you pass a char, it will actually pass an int,
which will probably not work.

Note: machines exist where the int representation is a
	subset of the real representation. However, even on these
	machines, a double is likely to be bigger than a real
	or an int.


Case: protoype at definition and call. (New-style rules)
--------------------------------------------------------

If you have a function declared with a prototype,
such as
	int f(real);
this indicates how the compiler is to call the function,
and indicates that the parameter is to be passed by whatever
conventions the compiler uses to pass real parameters.
If the argument in a call is, for example, an integer,
and the protoype indicates it should be a real,
it will be converted to real, and passed using the parameter
conventions for real. These might well differ from those for
passing a double.

Case: prototype at use but not at definition.
---------------------------------------------

This can be expected to happen during transition from
old-style to new.

If the call to function f occurs in a place where a prototype
has been declared for f, but the function definition
(probably in a separate compilation unit) uses old-style
parameter syntax, trouble is likely to arise.
The caller will perform a set of conversions based on the
prototype, and the function will expect the arguments
to have been converted without reference to a prototype.
If these conventions differ, the arguments get trashed
across the interface.  Ansi permits the old-new clash
provided that the prototype define the arguments to
have the types that they would have after conversion
according to the non-prototype parameter conversion rules.
This in general may differ from the types mantioned
in the function definition.

In the case of our function with the single real argument,
	f(r)real r;{    }
the protoype must read
	f(double);
because f will expect any real argument to have been
converted to double before it is passed.

Prototype at definition but not at use.
---------------------------------------

This is likely to occur during transition from old to new styles
if the function is called from a separate compilation unit,
or if the call occurs before the prototype or definition in
the same compilation unit.

Again, this will work if the prototype declared the types
that will result from the standard argument promotions.
If the function is defined using new-stype syntax, the
types of the parameters must be the same as the types in the
prototype declaration.

If a function with a single real argument is defined using
new-style syntax:
	g(real x){   }
then a call like g(r) will not work if the prototype for g is not
at the point of call.  Even if the argument r is in fact a real,
and g expects a real, absence of a protoype at the point of call
will force the real to be promoted to double and passed as a double!


The moral
---------

How do we use _NO_PROTO?

To make code work with and without prototypes, but to take advantage
of them when they are available, use the following conventions.

(a)	Include parameters in function declarations
	conditional on the absence of _NO_PROTO.
	This will provide extra checking and argument conversions
	and may detect a lot of silly errors.

(b)	Either 
	  - (b1) provide two versions of the parameter part of
	    a function definition, a new-style version
	    if _NO_PROTO is absent and  K&R style if it is
	    present,
	or
	  - (b2) use only the old K&R style, but accept restrictions
	    on the parameter types that occur in any prototypes.
	    The parameters in the prototypes must be the ones that
	    remain after the standard argument promotions,
	    because a K&R function will expect these instead
	    of the types actually mentioned in its code.
	    Thus if the function expects a char, the prototype
	    must lie and say int or unsigned int (depending on the
	    platform). It the function expects a real, the
	    prototype must say double.
	    
Note: For compatiblility with TAGS, the initial open
curly brace of the function body must be at the left margin
with the parameterds inside the #ifdef _NOPROTO . Otherwise
TAGS will fail to notice that a function was defined at all.

Note: For non-Visual-Edge readers on the net:
_NO_PROTO is a preprocessor symbol extensively used by
 many software producers to govern the
presence or absence of prototypes in header files.
Its use is not dictated or mentioned by the ANSI standard.

-- 
-------------------------------------------------------
Try one or more of the following addresses to reply.
at work:	hendrik@vedge.com,  iros1!vedge!hendrik
at home:	uunet!altitude!ozrout!topoi!hendrik