ANSI-C library functions can be generic intrinsic operators?

Tom MacDonald uunet!fig.cray.com!tam
Fri Apr 19 14:52:18 PDT 1991


Before giving my two cents worth on this subject I thought it would be
of public service to inform everyone that the term "ANSI C" is no longer
considered socially acceptable amongst the C elite because there is
an "ISO C" that is word for word compatible with the ANSI Standard.
Currently there is a letter ballot out to X3J11 members enquiring weather
or not the current ANSI C Standard should be discarded and replaced with
the current ISO C standard.  So - next time your at a party with international
C representatives in attendance, please remember to say "Standard C" so
everyone doesn't stare at you and whisper behind your back.

   [end tongue in cheek]

> Enough people have said so that I am beginning to believe it's not only true
> but reflects the intent of X3J11, so the question is, does anybody DISAGREE
> with the following statement about ANSI-C compilers:
> 
> 	An ANSI-C compiler may treat the identifiers defined in the Library
> 	Chapter 4 as reserved words having the specified semantics without
> 	regard to whether a function prototype or any other user-supplied
> 	definition is in scope.

All library functions are reserved for the behavior defined in the
standard.  For instance, a conforming implementation may gripe about
a user defining an external function named `tan' and expecting that function
to be called instead of the implementation's function with the same
name.  In fact, the implementation may produce inline code for any call
to a standard library function call (modulo the presence of #undef).

This is different from saying the library function names are keywords.
For instance, it is possible to define a `static' function named `tan'
because it is local to the compilation unit and the standard only reserves
external names.

	#include <stdio.h>
	static void tan(int n) {
	   while (n-- > 0)
		printf("A color that can't decide if it's brown or not.\n");
	}

	main() {
		tan(2);
		return 0;
	}

This is a conforming program that should not break because an implementation
decides to treat the call to `tan' as a call to a library function.  I
confess that early versions of the Cray Standard C compiler misbehaved
and tried to treat this call to `tan' as a math intrinsic call.  I have
been severely punished for that mistake by being forced to work on a
massively parallel architecture that supports IEEE arithmetic.
(Honest - I'm only kidding).

Similarly, there is nothing preventing an `auto' variable, a structure member,
a label, a formal parameter, and other local names from having the name `tan'.
Only external names are reserved.

I believe it is entirely acceptable to diagnose the following:

	main() { double x = sqrt(2); }

because the <math.h> header is not included and by the rules of C an
undeclared function is viewed as an external function that implicitly
returns an `int'.  This conflicts with the standard because the external
name `sqrt' is reserved as a function that returns a `double' and accepts
one `double' argument.  As a reference, the Cray Standard C Compiler (version
3.0) issues the following warning:

  The call to "sqrt" does not match the standard math function prototype.

> In particular, consider these cases:
> 
> 	printf(" xyz ...", p1, p2, p3, ...) can be recognized by the compiler
> 	and the right thing done (for instance checking correspondence of
> 	parameters to formats) whether or not a prototype is in scope,
> 	even in the case where the varargs calling sequence is different
> 	from the normal one. (In that case user-defined varargs functions
> 	will not work unless a proper varargs prototype is in view at every
> 	point where the function is invoked).

I agree with what is said here.

> 	sqrt(expression) can be recognized by the compiler and appropriate
> 	code generated to return a value of the same type as the expression.

I agree with this as long as no `static' version of the function is visible.

> 	what effect does #undef sqrt have on subsequent interpretation of
> 	"sqrt"?  It's intended to force a genuine function invocation, and
> 	is essential if a standard function is to be passed by a pointer
> 	since math.h may be written
> 
> 		extern double sqrt(double);
> 		#define sqrt __generic_intrinsic_sqrt

First - let me short circuit any comments about the recently exposed hole
in the C standard about function call operators not defining the result type
of the operator.  Let's assume the standard states the result type of a
function call operator with the first operand having type: "pointer to function
returning type" is "type".  Without some information like this all function
calls are unspecified behavior and that makes any discussion on this subject
worthless.

Second - I do not believe that this is a conforming implementation because
the macro __generic_intrinsic_sqrt, as defined, would conflict with structure
member names, label names, local automatic names, etc.  Including the <math.h>
header does not grant license to distort all names that have the same
spelling as the library functions.  However, the following is perfectly
acceptable:

		extern double sqrt(double);
		#define sqrt(X) __generic_intrinsic_sqrt(X)

because it limits itself to function call like expressions.

I think the real intent of this question is about the #undef semantics.
Ask yourself this question when trying to answer strict conformance
questions.  Is it possible to write a strictly conforming program that
performs an experiment demonstrating incorrect behavior?  If someone
puts "#undef sqrt" into their program and the implementation still
treats calls to `sqrt' as intrinsic calls then who can tell?  The
intent (IMHO) of the `#undef' semantics is to force implementations
to provide true entry points for all library functions.

	#include <math.h>
	#include <stdio.h>
	main() {
	   double x = (sqrt)(2.0);
	   printf("x = %f\n", x);
	   return 0;
	}

The above is strictly conforming even though `sqrt' is not recognizable
as a macro invocation.  The library entry point must exist.

> Can sqrt(float) return a float?

Let's try to think of an experiment we could perform that would answer
this question.  First off, the standard says that `sqrt' must return
a `double'.  For instance:

	if ( sizeof(double) != sizeof( sqrt(float_var) + 1.0f) )
	   printf("Nonconforming implementation\n");

It does return a `double'!  Also, on an implementation like Cray Research's
where `float' and `double' have identical representations this is not
a very interesting question.  So - let's assume that type `double' has
greater precision and possibly range (for instance IEEE double precision)
than type `float' (how about IEEE single precision).

Now, let's say your implementation treats "sqrt(float)" as identical
to "sqrtf(float)" (with `sqrtf' being a `float' version of `sqrt' as
reserved by the standard for future use).  First attempt at an experiment
might be:

	if ( (sqrt)(float_var)  !=  sqrt(float_var) )
	   printf("Nonconforming implementation\n");

This doesn't work because the standard does not even guarantee that the
expression

	sqrt(2.0) == sqrt(2.0)

evaluates to TRUE.  So - I've reached the conclusion that, for the math
functions it is permitted to treat "sqrt(float_var)" as "sqrtf(float_var)"
thus obviating the need for a `sqrtf' function.  I believe that this
requires some fancy documentation about the accuracy of your math functions.
(I leave that as an exercise for to the reader).

> Can sqrt(long double) return a long double?

This one is harder to answer.  If the definition of your implementation
is such that you claim type `long double' maps onto IEEE double extended
and type `double' maps onto IEEE double precision, then I say the answer
is NO you cannot return a `long double' result nor pass a `long double'
argument.  The standard explicitly states that the argument is converted
to the type of the parameter in the prototype.  In this case you are
converting to a representation that has fewer bits.  This is quite
different from converting `float' to `double' because although the value
is unchanged in the conversion there is this unspecified behavior
about the accuracy of the return value from the math library routines.
I don't see that this argument can be used to circumvent required
conversions especially when going from a representation with more
precision and possibly more range down to a smaller precision and
smaller range.  At least not in all cases.  It doesn't mean that

	double_var = long_double_var;
	sqrt(double_var) == sqrt(long_double_var)

evaluates to TRUE but it does mean that the result returned is represented
as type `double' and the implementation must define the representation
of type double.  For functions such as `pow' where RANGE errors can
occur, they occur for different `double' values than `long double' values.

> Can sqrt(int) return an int?

Let me answer this question in another way:

	#include <math.h>
	/* ... */

	{
	extern int i;
	double x = pow(i, 2);
	printf(x = %f\n");
	}

If the value `i' squared is not representable as an `int' but is
representable as a `double' as defined by your implementation, then
converting the above example to:

	#include <math.h>
        /* ... */ 
 
        { 
        extern int i;
        double x = i * i;
        printf(x = %f\n");  
        }

is not standard conforming because "i * i" does not behave as if `pow'
were actually called.

Therefore,  I do not believe it is correct to implement the standard
math functions as being identical to, for instance, Fortran intrinsics.
However, I do believe a good case can be made that the `f' suffix
versions of the math functions are not needed.  I also think the
widest-scanned semantics can be enhanced to provide this kind of
functionality.  X3J11 seems interested in catering to the needs
of numeric and scientific programmers.  They recognize that much of the
floating-point semantics are fuzzy and cleaning them up should be done
in a reasonable way.

(Now if they'd only recognize that functions returning structures
that overlap with the lvalue being assigned to is not NOT something
to cater to they'd really have something <grin>.)

Tom MacDonald



More information about the Numeric-interest mailing list