From: torek@horse.ee.lbl.gov (Chris Torek)
Newsgroups: comp.lang.c
Subject: Re: (cast) proposal (WAS: functions as arguments in DEC C)
Date: 26 Jan 1993 17:09:19 GMT
Organization: Lawrence Berkeley Laboratory, Berkeley CA
NNTP-Posting-Host: 128.3.112.15

In article <1993Jan26.135214.9306@dxcern.cern.ch> burow@dxcern.cern.ch
(Burkhard Burow) writes:
>My application can't [use] '(int (*)())a' because it doesn't know that
>a pointer to a function returning int is expected.
>It just knows that a pointer to function is expected.

There "ain't no sech animal" in C.  *EVERY* C function has a type.
That type WILL APPEAR when a pointer to that function is created.  You
can past a new, different label on it, and then paste the original
label back on, but C does not support an `unlabelled' version of such
pointers.  (C *does* have some fairly weak support for `unlabelled'
data pointers, in the form of `void *', but it lacks an equivalent for
function pointers, mainly due to lack of utility.  Old or Classic C
did not have this `unlabelled' type; one simply used `char *'.)

>QQQQuestion: Does C provide a cast, other than (void *) to do the following?
>
>#include <stdlib.h>
>
>void f(int a[3][5]) { a[0][0] = 1; return; }
>
>typedef ?????  CAST;
>
>void g(int *b)
>{
>f( (CAST) b);
>}

>CONSTRAINT:
>My application, generating the intermediate function g,
>knows the type of the array 'b',     in this case 'int',
>and the number of dimensions of 'b', in this case 2
>but it doesn't know the size of the individual dimensions, here 3 and 5.

This is no good:  C does not have the type `array ? of array ? of ...'
(where `?' means `unknown size').  Array sizes may only be omitted
in some very particular situations.  The underlying principle here
is that the size may be omitted only when it is not needed for
address calculations.  Since the size of the `nested' array *is*
needed---the address of a[1][2] in f(), for instance, is calculated
as `base of array plus 1*5 plus 2'---it may not be omitted.

If this were not the case, (some) pointers would have to have extra
information.  Such a proposal (or rather, several such proposals) have
been made for the `numerical extentions' version of C.  I personally
favor Ritchie's `fat pointer' method; it has a number of advantages
over GCC's `FORTRAN-like' method.  These are perhaps best done
by illustration:

	/* gcc method */
	void f(int m, int n, double a[m][n]) {
		...
	}

Inside the function f, the pointer to which `a's declaration decays has
type `pointer to array n of double', where n is a *variable* rather
than a constant.  This means either that:

	The variable n must stay in scope, and its value must not
	be overwritten.

or

	The original value of n must be saved at function entry
	and somehow invisibly associated with the array `a' within
	the compiler.

The scope requirement in the former happens naturally (the compiler can
always `peek out' of an inner scope that defines a new `n'; the only
requirement is that the outer n still exist, which it always will until
`a' is gone).  The constance could be had by forcing n to be `const
int' instead of just `int', but this is not currently required in gcc.
The latter version seems more sensible, but still gives me a creepy
feeling of `unnecessarily complicated and error-prone machinery'.  (One
could, for instance, pass the wrong value for n, with disastrous
consequences.)

	/* fat pointer method */
	/* I have arbitrarily used my `?' syntax here */
	void f(double a[][?]) {
		...
	}

In this case the pointer has type `pointer to array passed-size of
double'.  This size is accessible as `sizeof a[0] / sizeof double' or
`sizeof *a / sizeof double'.  Since it does not have a variable name,
however, one cannot expect it to `act like' a variable.  These `fat
pointers' are really `dope vectors' in disguise.

Note that both methods require additional compiler machinery, over and
above what is now required to implement ANSI C.

>QQQuestion/PPProposal:
>Why doesn't C allow 'incomplete casting' in order to allow one to best match
>the prototyping of the arguments of functions?

Mainly due to the additional machinery.  Aside from gcc's method, none
of these have been widely implemented, so there was no experience to
draw on when making the ANSI C standard.

>i)   pointer to function >cast> pointer to function returning void or any type:
>     ((*)())

Remember that C can be implemented on machines that are nearly
arbitrarily perverse.  For instance, consider the following:

	char = 8 bits
	short = 16 bits
	int = 32 bits
	long = 64 bits

	pointer to char = 96 bits (used via `byte pointer' registers bp0..bp3)
	pointer to short = 64 bits (used via `subaddress' registers sp0..sp2)
	pointer to int = 64 bits (also used via subaddress registers)
	pointer to long = 32 bits (used via `address' registers ap0..ap6)

	pointer to function returning void = 64 bits (address of procedure)
	pointer to function returning int = 96 bits
		(address of procedure + pointer to value slot)
	pointer to function returning char = 160 bits
		(address of procedure + pointer to value slot)

On such a machine, `void *' (a `pointer to any data') must have the
`longest' object pointer type, in this case, 96 bits.  It would be
possible to define a `pointer to any_function' type; again, this would
have to have the largest size, or 160 bits.

As it turns out, in practise, ANSI C already requires the compiler for
this machine to make *all* function pointers 160 bits, or else come up
with some method for `reconsitituting' the 160-bit value when a
`pointer to function returning char' is turned temporarily into a
`pointer to function returning void'.  (In practise again, the most
convenient way to implement C on such a machine is typically to
implement all function pointers via dope vectors or handles, but
never mind that.)

Since the value slot pointer does not exist for `procedures' (functions
returning void), but does for others, it is clearly a mistake to call
functions via procedure pointers and vice versa.  Moreover, since the
value slot pointer is different sizes for different return types, the
`actual underlying type' must be used.  Otherwise the value slot
pointer will be passed in the wrong register (bp3 instead of ap6,
for instance).

			*	*	*	*

Now in fact, the original problem brought up seems to be `calling
between C and FORTRAN'.  As soon as we bring other languages into this,
the issue gets *much* cloudier.  The C standard says nothing about
interlanguage calling.  All we can say for certain is that, in ANSI
C, code like the following is entirely legal.

	#include <math.h>
	#include <stdio.h>
	#include <stdlib.h>

	typedef void (*gfp)(void);	/* generic function pointer */
	typedef void *gdp;		/* generic data pointer */

	#define F_INT_RET_INT	0	/* e.g., abs */
	#define	F_DBL_RET_DBL	1	/* e.g., sin, cos */

	void silly(gfp fn, gdp val, int realtype) {
		switch (realtype) {

		case F_INT_RET_INT:
			*(int *)val = ((int (*)(int))fn)(-3);
			return;

		case F_DBL_RET_DBL:
			*(double *)val = ((double (*)(double))fn)
			    (.78539816339744830961);
			return;

		default:
			(void)fprintf(stderr, "panic: silly: badrealtype\n");
			abort();
		}
	}

	int main(void) {
		double root2over2;
		int three;

		/*
		 * No casts are required for the 2nd arg as a prototype
		 * is in scope, but we will use one anyway.
		 */
		silly((gfp)abs, (void *)&three, F_INT_RET_INT);
		silly((gfp)sin, (void *)&root2over2, F_DBL_RET_DBL);
		(void)printf("this should be 3: %d\n", three);
		(void)printf("this should be .7071...: %.10f\n", root2over2);
		exit(EXIT_SUCCESS);
	}

The following, however, is wrong:

		silly((gfp)abs, (void *)&three, F_DBL_RET_DBL);

The call to silly() is correct, but silly() will then attempt to
call abs() through a `pointer to function (double) returning double'.
The result is undefined.

If one is calling a FORTRAN function, rather than `silly', one must
provide to that function whatever type it expects.  It is rather
bizarre to ask a C standard to say what FORTRAN must expect, or vice
versa.  If the FORTRAN module expects a `pointer to function (double)
returning double', passing `sin' directly will work.  If it expects
a `pointer to function (void) returning void', and internally converts
it to `pointer to function (double) returning double', then passing
`(gfp)sin' will work.  But we just cannot say *what* it expects---after
all, this is comp.lang.c, not comp.lang.interlanguage.  Indeed, it
may even expect a type that does not *exist* in C.  Your only option
is to look in your vendor's documentation and see if they tell you
what to do.
-- 
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