Parameter Types in Old-Style Function Definitions

Kee Hinckley nazgul at alphalpha.com
Sat Sep 8 15:34:08 AEST 1990


In article <2689 at dataio.Data-IO.COM> bright at Data-IO.COM (Walter Bright) writes:
>In article <10391 at pt.cs.cmu.edu> hjelm at g.gp.cs.cmu.edu (Mark Hjelm) writes:
><What is the compiler allowed/required to do for this:
><    f(a, b)
><        float a;
><        char b;
><    {
><    }
>
>It is semantically equivalent to:
>	f(atmp,btmp)
>	double atmp;
>	int btmp;

That part I understand.  I have a related question though.  Consider
the following function, defined in K&R:

foo(c, i)
char	c;
int	i;
{}

Clearly the function expects to pick two ints up off of the stack.

Now consider the same function, declared and used from ANSI C (or C++).

extern foo(char c, int i);
foo('g', 0xF0F0);

As far as I can tell the ANSI spec (but I'm just reading it through the
second edition K&R book) doesn't explicitly address the compatibility
issue raised here.  In other words, in some implementations the compiler
may do an explicit promotion of 'char c' to 'int', even though it retains
the correct semantics.  Whereas other compilers may actually put a single
byte on the stack, in which case the call will not work properly.  I note
that the X Intrinsics seem to recognize this problem and define both
"Wide" and "Narrow" prototypes for the functions, where the "Wide" prototypes
explicitly redefine all small integral types to "int" and the "Narrow" ones
leave them be.  The default, for compatibility reasons, is "Wide".

So I guess the question is.  Does the ANSI spec mandate that the above be
compatible, mandate that they aren't, or not say?  And, does it make any
difference whether the definition of the function (same syntax) is compiled
using a K&R or an ANSI compiler?

Here is a sample program to test this with, consisting of two files, the
first of which must be compiled with a prototyping compiler, the second
of which may be compiled with or without one.  Bind them together and see
what happens. 

/*
 *
 *	p1.c
 *
 * Here are the combinations:
 *	declared			defined
 *	exact prototype			exact prototype
 *	exact prototype			wide prototype
 *	exact prototype			no prototype
 *	wide prototype			exact prototype
 *	wide prototype			wide prototype
 *	wide prototype			no prototype
 *	no prototype			exact prototype
 *	no prototype			wide prototype
 *	no prototype			no prototype
 *
 * and, since I here GNU cheats on this and uses ANSI semantics even
 * if the definition is K&R style
 *
 *	exact prototype			K&R wide
 *	wide prototype			K&R wide
 *	no prototype			K&R wide
 */

extern void	EE(char c, int i);
extern void	EW(char c, int i);
extern void	EN(char c, int i);

extern void	WE(int c, int i);
extern void	WW(int c, int i);
extern void	WN(int c, int i);

extern void	NE();
extern void	NW();
extern void	NN();

extern void	EG(char c, int i);
extern void	WG(int c, int i);
extern void	NG();

void main()
{
    EE('x', 999);
    EW('x', 999);
    EN('x', 999);

    WE('x', 999);
    WW('x', 999);
    WN('x', 999);

    NE('x', 999);
    NW('x', 999);
    NN('x', 999);

    EG('x', 999);
    WG('x', 999);
    NG('x', 999);
}


/*
 *	p2.c
 *
 * Here are the combinations:
 *	declared			defined
 *	exact prototype			exact prototype
 *	exact prototype			wide prototype
 *	exact prototype			no prototype
 *	wide prototype			exact prototype
 *	wide prototype			wide prototype
 *	wide prototype			no prototype
 *	no prototype			exact prototype
 *	no prototype			wide prototype
 *	no prototype			no prototype
 *
 * and, since I here GNU cheats on this and uses ANSI semantics even
 * if the definition is K&R style
 *
 *	exact prototype			K&R wide
 *	wide prototype			K&R wide
 *	no prototype			K&R wide
 */

#include <stdio.h>

#ifdef __STDC__
void	EE(char c, int i) {
    printf("Exact Exact:	'%c', %d\n", c, i);
}
void	EW(int c, int i) {
    printf("Exact Wide: 	'%c', %d\n", c, i);
}
#endif
void	EN(c, i) 
char	c;
int	i;
{
    printf("Exact None: 	'%c', %d\n", c, i);
}


#ifdef __STDC__
void	WE(char c, int i) {
    printf("Wide Exact: 	'%c', %d\n", c, i);
}
void	WW(int c, int i) {
    printf("Wide Wide:  	'%c', %d\n", c, i);
}
#endif
void	WN(c, i)
char	c;
int	i;
{
    printf("Wide None:  	'%c', %d\n", c, i);
}


#ifdef __STDC__
void	NE(char c, int i) {
    printf("None Exact: 	'%c', %d\n", c, i);
}
void	NW(int c, int i) {
    printf("None Wide:  	'%c', %d\n", c, i);
}
#endif
void	NN(c, i)
char	c;
int	i;
{
    printf("None None:  	'%c', %d\n", c, i);
}


void	EG(c, i)
int	c, i;
{
    printf("Exact Wide-K&R:	'%c', %d\n", c, i);
}
void	WG(c, i)
int	c, i;
{
    printf("Wide Wide-K&R:	'%c', %d\n", c, i);
}
void	NG(c, i)
int	c, i;
{
    printf("None Wide-K&R:	'%c', %d\n", c, i);
}

---

Here is the result of running this on an Apollo.

Exact Exact:    'x', 999
Exact Wide:     '', 65498744
Exact None:     '', 65498744
Wide Exact:     '', 7864320
Wide Wide:      'x', 999
Wide None:      'x', 999
None Exact:     '', 7864320
None Wide:      'x', 999
None None:      'x', 999
Exact Wide-K&R: '', 65471463
Wide Wide-K&R:  'x', 999
None Wide-K&R:  'x', 999

Basically Exact is Exact and it isn't compatible with anything that
wasn't defined without a prototype, Wide and None are identical.
This risks compatibility with old code (you have to be careful how
you define things and you never want to inconsistantly use prototypes
in a single program).  On the other hand, it enhances compatibility
with other languages.  
-- 
Alphalpha Software, Inc.	|	motif-request at alphalpha.com
nazgul at alphalpha.com		|-----------------------------------
617/646-7703 (voice/fax)	|	Proline BBS: 617/641-3722

I'm not sure which upsets me more; that people are so unwilling to accept
responsibility for their own actions, or that they are so eager to regulate
everyone else's.



More information about the Comp.std.c mailing list