From: torek@horse.ee.lbl.gov (Chris Torek)
Newsgroups: comp.lang.c
Subject: Re: Auto. conversion of function arguments
Date: 9 Nov 1992 13:58:10 GMT
Organization: Lawrence Berkeley Laboratory, Berkeley
Reply-To: torek@horse.ee.lbl.gov (Chris Torek)
NNTP-Posting-Host: 128.3.112.15

In article <1229@jagubox.gsfc.nasa.gov> jim@jagubox.gsfc.nasa.gov
(Jim Jagielski) writes:
>Assume you have a function that accepts a char parameter. Now we know
>that whenever an integral argument is passed by a function call, chars
>and shorts are promoted to int...

This used to be the case.  Now, however, with ANSI C and prototypes,
the picture has changed.

>Sooo... assuming you call a function that accepts 'char', is there any
>_real_ reason why you must cast shorts and ints to char in the function
>call itself?

There are three cases.

 A. The function is defined using old-style syntax:

	int f(c) char c; { ... }

 B. The function is defined using new prototype syntax, but at the
    call site, the function is either not declared at all (and hence
    gets implicitly declared), or is declared using old-style syntax.

 C. The function is defined using new prototype syntax, and is properly
    (and fully) declared at the call site (i.e., the prototype is in
    scope).

Now, if one or more of the arguments have `narrow' types (char, short,
unsigned char, unsigned short, or float), case B is in fact a bug in
the source, but not one that requires a diagnostic.  (A good compiler
may be able to provide one anyway, but you cannot count on it.)

>After all, the parameter always "arrives" at the function as an int ...

In cases A and B, this is true.  In case C, it may or may not be true,
depending on the implementation.  In case B, while the parameter
actually arrives in widened form, the implementation may *expect* the
narrow form; this is why case B is a program bug.  (If the
implementation happens to use the widened form internally---for,
say, backwards compatibility---then case B will `work' on that system,
but it is still a program bug.)

Assuming that case B is no longer of interest (since the behavior of
buggy programs is system-dependent), let us forge along with cases A
and C.

>so it "appears" that another automatic conversion takes place:  from
>the [widened type] that arrives to the [narrow] type of the parameter

True for case A; implementation-dependent for case C.

This means the code is guaranteed to work in case A.

In case C, the presence of the prototype causes the actual parameter to
act as if it were an assignment.  In this case, even if the implementation
really does use narrow parameters internally, the code must still work:
the assignment causes conversion.

Thus, both of these complete fragments[%] are legal:
-----
% By this I mean more code is needed to make a complete program, but
  these fragments are legal as a separate file, and when joined with
  a legal main() must work.
-----

    /* fragment 1 (compatible with truly ancient implementations) */
	#include <stdio.h>

	callit() { (void)put('x'); }
	int put(c) char c; { return fwrite(&c, 1, 1, stdout); }

    /* fragment 2 (for ANSI implementations) */
	#include <stdio.h>

	int put(char c) { return fwrite(&c, 1, 1, stdout); }
	void callit(void) { (void)put('x'); }
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 510 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov
Newsgroups: comp.lang.c