How We Got Here
The C syntax for declaring function points was intended to mirror usage. Consider a regular function declaration like this from <math.h>
:
double round(double number);
To have a point variable you can assign that to with type safety using
fp = round;
you’d need to have declared that fp
point variable this way:
double (*fp)(double number);
So all you have to do is look at how you would use the function, and replace the name of that function with a pointer reference, making round
into *fp
. However, you need an extra set of parens, which some would say makes it a bit messier.
Arguably, this used to be easier in the original C, which didn’t even have function signature, but let’s not go back there, ok?
The place it becomes especially nasty is figuring out how to declare a function that either takes as an argument or returns a pointer to a function, or both.
If you had a function:
void myhandler(int signo);
you could pass it to the signal function(3) this way:
signal(SIGHUP, myhandler);
or if you want to keep the old handler, then
old_handler = signal(SIGHUP, new_handler);
which is pretty easy. What is pretty easy — nor pretty, nor easy — is getting the declarations right.
signal(int signo, ???)
Well, you just go back to your function declaration and swap the name for a point reference:
signal(int sendsig, void (*hisfunc)(int gotsig));
Because you are not declaring gotsig
, you might find it easier to read if you omit:
signal(int sendsig, void (*hisfunc)(int));
Or maybe not. :(
Except that that is not good enough, because signal(3) also returns the old handler, as in:
old_handler = signal(SIGHUP, new_handler);
So now you have to figure out how to declare all those.
void (*old_handler)(int gotsig);
is enough for the variable you’re going to assign to. Note that you are not really declaring gotsig
here, only old_handler
. So this is really enough:
void (*old_handler)(int);
That brings us to a correct definition for signal(3):
void (*signal(int signo, void (*handler)(int)))(int);
Typedefs to the Rescue
By this time, I think everyone will agree that that’s a mess. Sometimes it’s better to name your abstractions; often, really. With the right typedef
, this becomes much easier to understand:
typedef void (*sig_t) (int);
Now your own handler variable becomes
sig_t old_handler, new_handler;
and your declaration for signal(3) becomes just
sig_t signal(int signo, sig_t handler);
which is suddenly comprehensible. Getting rid of the *’s also gets rid of some of the confusing parentheses (and they say parens always make things easier to understand — hah!). Your usage is still the same:
old_handler = signal(SIGHUP, new_handler);
but now you have chance of understanding the declarations for old_handler
, new_handler
, and even signal
when you first encounter them or need to write them.
Conclusion
Very few C programmers, it turns out, are capable of devising the correct declarations for these things on their own without consulting reference materials.
I know, because we once had this very question on our interview questions for people doing kernel and device driver work. :) Sure, we lost a lot of candidates that way as they crashed and burned on the whiteboard. But we also avoided hiring people who claimed they had previous experience in this area but couldn’t actually do the work.
Because of this widespread difficulty, though, it is probably not just sensible but indeed reasonable to have a way to go about all those declarations that no longer require you to be a triple-alpha geek programmer sitting three sigmas above the mean just to use this sort of thing comfortably.
f :: (Int -> Int -> Int) -> Int -> Int
– Karoly Horvath Aug 18 '11 at 8:08function strlen(s:String):int {...}
. Also, typed lambda calculus (hence, Haskell). – outis Aug 18 '11 at 8:10