... you can read this:
int (*fn)(int) = f;
as "fn is a pointer, and *fn is a function that takes an int and return
an int". ...
I'd appreciate a better explanation of function type syntax myself. I
think I've just got used to them rather than ever understood them.
The above is exactly the right description.
Dennis has said that the rule for C is "declaration mirrors use".
If one uses (*p)[3] to access one "double" object, then:
double (*p)[N];
is probably the actual declaration (and N is at least 4), giving
p the type "pointer to array N of double". Thus *p has type
"array N of double", and (*p)[3] has type "double".
Of course, (*p) and (p[0]) do the same thing in actual code, so
it is possible to write p[0][3] instead of (*p)[3]. But that
would imply a declaration like:
double p[M][N];
for some integral constants M > 0 and N > 3. Hence, "declaration
mirrors use" only if you happen to use the same format as the
declaration. This also causes confusion with function pointers:
#include <math.h>
...
double result, argument;
double (*fp)(double);
if (use_sine)
fp = sin;
else /* use cosine */
fp = cos;
...
result = (*fp)(argument);
As with arrays and pointers, function names "decay" to pointers to
those functions in most contexts. Here (*fp) "follows the pointer"
in "fp" to the appropriate function (sin or cos), then "un-follows"
it (via "decay") to get the pointer, and only then does the call,
using the pointer. So we can drop the asterisk, and write:
result = fp(argument);
This is the same technique (though a different end-point, as it
were) that we use when we write:
double some_table[SOME_SIZE];
double *dp;
...
dp = some_table;
instead of "dp = &some_table[0]".
Probably the most confusing of all C's type syntaxes is that for
casts (and, correspondingly, unnamed prototype arguments). To form
a proper cast, start by writing a declaration for a variable of
the given type. Let us tackle a tough one: a pointer to a function
that handles a signal. ANSI/ISO C says that a signal function
receives one "int" (the signal number) and returns "void". So the
function itself might look like:
void handle_signal(int sig) {
... some code here ...
}
A pointer to such a function has type:
pointer to function (int) returning void
which is declared as:
void (*fp)(int);
To turn this into a cast, start with the declaration and remove
the name:
void (*)(int)
Then just add parentheses around the whole thing, giving:
(void (*)(int))
Of course, you have to cast some actual value, such as 0. Add
parentheses to that "just in case", and you get a typical definition
for SIG_DFL:
#define SIG_DFL ((void (*)(int))0)
In a function prototype, the arguments can have names, or omit them:
double cos(double argument);
double sin(double);
In an implementation header -- one provided by an implementor -- the
implementor has to be careful not to use a user's name. Imagine
the troubles that occur if the user writes, for instance:
#define i "me, myself"
#include <stdlib.h>
and <stdlib.h> says something like:
int abs(int i);
which then expands to:
int abs(int "me, myself");
Hence, if you look at actual implementor's headers, they tend to use
either:
int abs(int); /* no identifier = no chance for the user to #define it */
or:
int abs(int __i); /* __ prefix is reserved, keep your hands off! */
So consider signal(), again. Its first argument is an "int",
the signal number; its second argument is a pointer to a signal
function.
We already came up with the spelling for the type "pointer to
function taking one int and returning void":
void (*)(int)
So signal's two arguments, using no names, are:
____ int, void (*)(int) ____
(where ____ represents the blanks to fill in later).
However, signal's return value has the same type as its second
argument: "pointer to function taking one int and returning void".
To spell *that*, without using a typedef, we have to declare
signal as:
void (*signal( ____ ))(int);
where ____ represents "the arguments to the actual function" (again,
"blanks to fill in later"). The parentheses around (*signal( ____ ))
are required to force the "*" to bind to the "signal(int)" part,
and not to the "void" part.
Finally, we can drop in the arguments, which we already worked out:
void (*signal(int, void (*)(int)))(int);
and we have a correct prototype for signal(), using no names. If
we are playing implementor -- writing <signal.h> -- and choose to
put in names, we have to use our (implementor) reserved names with
double underscores or similar, and write:
void (*signal(int __sig, void (*__func)(int)))(int);
As an ordinary (non-implementor) C programmer, of course, we would
have to *avoid* the double-underscore names.
This is a lot easier with a typedef, in part because signal's second
parameter -- "pointer to function taking int and returning void"
-- has the *same* type as its return value. So one typedef,
for "pointer to function taking int and returning void", removes
all the hair:
typedef void (*signal_function_pointer)(int);
signal_function_pointer signal(int sig, signal_function_pointer func);
Of course, if we put on our Implementor Hats and write a *new*
<signal.h>, we find that we cannot define the name
"signal_function_pointer". (It is reserved to the user. User gets
the non-"__" names, implementor gets the "__" names, and the two
never collide.) So we either write the hairy version, or typedef
a double-underscore name.