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