[comp.std.c] Strange behaviour with old-style code

rsargent@alias.UUCP (Richard Sargent) (02/07/91)

I have come across a compiler which does something very unusual
in its handling of old-style functions. Is there anyone who knows
whether the following behaviour is either permitted or forbidden
by the ANSI Standard for C?

Thanks.

====================================================================
The compiler passes floats widened to doubles, and the routine pulls
the values off the stack using the appropriate widened addressing,
but then proceeds to use the "declared" argument type to do single
precision floating point arithmetic. A colleague of mine has
crawled through the Standard as well as K&R II, but found nothing
that explicitly addresses this issue.

The following program shows what happens:


	main()
	{
		printf("sizeof(float)=%d, sizeof(double)=%d\n",
			sizeof(float), sizeof(double));

		/* note: no prototypes in scope */
		bar(1.5);
	}

	int
	bar(u)
	float u;
	{
		printf("old style func: u=%f, sizeof(u)=%d\n", u, sizeof(u));
	}



The results are (under this particular compiler):

  sizeof(float)=4, sizeof(double)=8
  old style func: u=1.500000, sizeof(u)=4 


The call to bar() should be fine since both the call and the function
definition are old style K&R.  In the interest of upward compatibility
with the old K&R C, the formal and actual parameters to bar() are
supposed to be promoted to double and used as double.  
               ^^done^^               ^^^^ I question this.


Inquiring minds want to know...


p.s.
This example shows only a single argument used. The real code
used several arguments. All values were addressed from the
stack correctly. It is just the question of "using" the float
argument as a float, rather than the implicit "double".

scs@adam.mit.edu (Steve Summit) (02/09/91)

In article <1991Feb6.190607.11731@alias.uucp> rsargent@alias.UUCP (Richard Sargent) writes:
>I have come across a compiler which does something very unusual
>in its handling of old-style functions.
>The compiler passes floats widened to doubles, and the routine pulls
>the values off the stack using the appropriate widened addressing,
>but then proceeds to use the "declared" argument type to do single
>precision floating point arithmetic.

Actually, it looks like the compiler is behaving correctly.  If
you thought that the compiler should treat

	bar(u)
	float u;
	{
	...

"as if" u had been declared as a double, that is exactly what the
compiler is _not_ supposed to do.  The compiler is instead
supposed to do the equivalent of

	bar($u)
	double $u;
	{
	float u = $u;
	...

where $u is just a pseudo-name for the passed value, on the stack
or wherever.  As you may have suspected, the distinction is
significant if the address of u is passed to some other routine
which expects a pointer-to-float.

> A colleague of mine has
> crawled through the Standard as well as K&R II, but found nothing
> that explicitly addresses this issue.

X3.159, section 3.7.1, p. 82, lines 21-22: "On entry to the
function the value of each argument shall be converted to the
type of its corresponding parameter, as if by assignment to the
parameter."  The Rationale reasserts this, and explicitly states
that "Type rewriting [i.e. pretending that float arguments had
been declared double] is no longer permissible."

K&R2 spells this out in some fine print in section A10.1, on page
226:

	"There is... a small change in the details of promotion:
	the first edition specified that the declarations of
	float parameters were adjusted to read double.  The
	difference becomes noticeable when a pointer to a
	parameter is generated within a function."

The distinction is actually between the old interpretation of
old-style function definitions and the new interpretation of
those old definitions.  Your compiler appears to be correctly
following the new interpretation.  (I say "appears" because it
correctly reports sizeof(u) as 4.  There is a subtle, secondary
bug which is possible, depending on the machine's floating-point
formats.  Given a fortuitous byte order and bit arrangement, it
is often possible, or at least appears possible, essentially to
skip the local allocation and assignment

	float u = $u;

and generate code which treats the stack location $u as if it
were a float.  This works if the bits and bytes making up a float
are an initial subset of those making up a double, with the added
bits in a double simply adding precision.  For example, VAX F_
and D_floating formats satisfy this relationship.  However, the
Rationale warns that "Not many implementations can subset the
bytes of a double to get a float.  Even those that apparently
permit simple truncation often get the wrong answer on certain
negative numbers."  I am sure that someone else will expand on
this point if it is important.)

Note, by way of contrast, that since "Array expressions and
function designators as arguments are converted to pointers
before the call," it _is_ the case that "A declaration of a
parameter as `array of type' shall be adjusted to `pointer to
type,' and a declaration of a parameter as `function returning
type' shall be adjusted to `pointer to function returning type'".
(X3.159 section 3.7.1 again.)  That is, float (and char and
short) parameter declarations are not "rewritten," while array
and pointer parameters are.

                                            Steve Summit
                                            scs@adam.mit.edu

gwyn@smoke.brl.mil (Doug Gwyn) (02/09/91)

In article <1991Feb6.190607.11731@alias.uucp> rsargent@alias.UUCP (Richard Sargent) writes:
>I have come across a compiler which does something very unusual
>in its handling of old-style functions.

Looks to me like the arguments are default-promoted to widened types,
then assigned to local variables of the declared type in the function
definition.  This is exactly what is SUPPOSED to occur.  What did you
expect?

henry@zoo.toronto.edu (Henry Spencer) (02/10/91)

In article <1991Feb6.190607.11731@alias.uucp> rsargent@alias.UUCP (Richard Sargent) writes:
>The compiler passes floats widened to doubles, and the routine pulls
>the values off the stack using the appropriate widened addressing,
>but then proceeds to use the "declared" argument type to do single
>precision floating point arithmetic...

This is correct.  Only arrays and functions have their types silently
rewritten in function declarations.  Note 3.7.1:  "On entry to the
function the value of each argument expression shall be converted to
the type of its corresponding parameter, as if by assignment..."  So
an old-style function, absent prototypes, gets a float argument passed
as a double, and converts it to a float since the argument was declared
to be float.

(A subtle point here, evidently applicable in your case, is that it is
sometimes possible to convert a double to a float by simply ignoring
its low-order half.  Sounds like you're running on a VAX, where this
works.  Most modern machines use IEEE floating point, in which the trick
doesn't work because double has more exponent bits as well as more
mantissa bits, so the high-order half of a double isn't laid out the
same way as a float.)

K&R1 C silently rewrote float to double, but didn't rewrite char to int.
(Mostly because the ignore-lower-order trick is pretty dependable for
int->char but not for double->float.)  ANSI C rewrites neither.  This
point is actually discussed in the Rationale section for 3.7.1.
-- 
"Maybe we should tell the truth?"      | Henry Spencer at U of Toronto Zoology
"Surely we aren't that desperate yet." |  henry@zoo.toronto.edu   utzoo!henry