I have always been a bit stumped when I read other peoples' code which had typedefs for pointers to functions with arguments. I recall that it took me a while to get around to such a definition while trying to understand a numerical algorithm written in C a while ago. So, could you share your tips and thoughts on how to write good typedefs for pointers to functions (Do's and Do not's), as to why are they useful and how to understand others' work? Thanks!
相关问题
- Multiple sockets for clients to connect to
- What is the best way to do a search in a large fil
- glDrawElements only draws half a quad
- Index of single bit in long integer (in C) [duplic
- Equivalent of std::pair in C
A very easy way to understand typedef of function pointer:
Use typedefs to define more complicated types i.e function pointers
I will take the example of defining a state-machine in C
now we have defined a type called action_handler that takes two pointers and returns a int
define your state-machine
The function pointer to the action looks like a simple type and typedef primarily serves this purpose.
All my event handlers now should adhere to the type defined by action_handler
References:
Expert C programming by Linden
Consider the
signal()
function from the C standard:Perfectly obscurely obvious - it's a function that takes two arguments, an integer and a pointer to a function that takes an integer as an argument and returns nothing, and it (
signal()
) returns a pointer to a function that takes an integer as an argument and returns nothing.If you write:
then you can instead declare
signal()
as:This means the same thing, but is usually regarded as somewhat easier to read. It is clearer that the function takes an
int
and aSignalHandler
and returns aSignalHandler
.It takes a bit of getting used to, though. The one thing you can't do, though is write a signal handler function using the
SignalHandler
typedef
in the function definition.I'm still of the old-school that prefers to invoke a function pointer as:
Modern syntax uses just:
I can see why that works - I just prefer to know that I need to look for where the variable is initialized rather than for a function called
functionpointer
.Sam commented:
Let's try again. The first of these is lifted straight from the C standard - I retyped it, and checked that I had the parentheses right (not until I corrected it - it is a tough cookie to remember).
First of all, remember that
typedef
introduces an alias for a type. So, the alias isSignalHandler
, and its type is:The 'returns nothing' part is spelled
void
; the argument that is an integer is (I trust) self-explanatory. The following notation is simply (or not) how C spells pointer to function taking arguments as specified and returning the given type:After creating the signal handler type, I can use it to declare variables and so on. For example:
Please note How to avoid using
printf()
in a signal handler?So, what have we done here - apart from omit 4 standard headers that would be needed to make the code compile cleanly?
The first two functions are functions that take a single integer and return nothing. One of them actually doesn't return at all thanks to the
exit(1);
but the other does return after printing a message. Be aware that the C standard does not permit you to do very much inside a signal handler; POSIX is a bit more generous in what is allowed, but officially does not sanction callingfprintf()
. I also print out the signal number that was received. In thealarm_handler()
function, the value will always beSIGALRM
as that is the only signal that it is a handler for, butsignal_handler()
might getSIGINT
orSIGQUIT
as the signal number because the same function is used for both.Then I create an array of structures, where each element identifies a signal number and the handler to be installed for that signal. I've chosen to worry about 3 signals; I'd often worry about
SIGHUP
,SIGPIPE
andSIGTERM
too and about whether they are defined (#ifdef
conditional compilation), but that just complicates things. I'd also probably use POSIXsigaction()
instead ofsignal()
, but that is another issue; let's stick with what we started with.The
main()
function iterates over the list of handlers to be installed. For each handler, it first callssignal()
to find out whether the process is currently ignoring the signal, and while doing so, installsSIG_IGN
as the handler, which ensures that the signal stays ignored. If the signal was not previously being ignored, it then callssignal()
again, this time to install the preferred signal handler. (The other value is presumablySIG_DFL
, the default signal handler for the signal.) Because the first call to 'signal()' set the handler toSIG_IGN
andsignal()
returns the previous error handler, the value ofold
after theif
statement must beSIG_IGN
- hence the assertion. (Well, it could beSIG_ERR
if something went dramatically wrong - but then I'd learn about that from the assert firing.)The program then does its stuff and exits normally.
Note that the name of a function can be regarded as a pointer to a function of the appropriate type. When you do not apply the function-call parentheses - as in the initializers, for example - the function name becomes a function pointer. This is also why it is reasonable to invoke functions via the
pointertofunction(arg1, arg2)
notation; when you seealarm_handler(1)
, you can consider thatalarm_handler
is a pointer to the function and thereforealarm_handler(1)
is an invocation of a function via a function pointer.So, thus far, I've shown that a
SignalHandler
variable is relatively straight-forward to use, as long as you have some of the right type of value to assign to it - which is what the two signal handler functions provide.Now we get back to the question - how do the two declarations for
signal()
relate to each other.Let's review the second declaration:
If we changed the function name and the type like this:
you would have no problem interpreting this as a function that takes an
int
and adouble
as arguments and returns adouble
value (would you? maybe you'd better not 'fess up if that is problematic - but maybe you should be cautious about asking questions as hard as this one if it is a problem).Now, instead of being a
double
, thesignal()
function takes aSignalHandler
as its second argument, and it returns one as its result.The mechanics by which that can also be treated as:
are tricky to explain - so I'll probably screw it up. This time I've given the parameters names - though the names aren't critical.
In general, in C, the declaration mechanism is such that if you write:
then when you write
var
it represents a value of the giventype
. For example:In the standard,
typedef
is treated as a storage class in the grammar, rather likestatic
andextern
are storage classes.means that when you see a variable of type
SignalHandler
(say alarm_handler) invoked as:the result has
type void
- there is no result. And(*alarm_handler)(-1);
is an invocation ofalarm_handler()
with argument-1
.So, if we declared:
it means that:
represents a void value. And therefore:
is equivalent. Now,
signal()
is more complex because it not only returns aSignalHandler
, it also accepts both an int and aSignalHandler
as arguments:If that still confuses you, I'm not sure how to help - it is still at some levels mysterious to me, but I've grown used to how it works and can therefore tell you that if you stick with it for another 25 years or so, it will become second nature to you (and maybe even a bit quicker if you are clever).
cdecl
is a great tool for deciphering weird syntax like function pointer declarations. You can use it to generate them as well.As far as tips for making complicated declarations easier to parse for future maintenance (by yourself or others), I recommend making
typedef
s of small chunks and using those small pieces as building blocks for larger and more complicated expressions. For example:rather than:
cdecl
can help you out with this stuff:And is (in fact) exactly how I generated that crazy mess above.
This is the simplest example of function pointers and function pointer arrays that I wrote as an exercise.
Output of this is :
22
6
Note that, same math_func definer has been used for the declaring both the function.
Same approach of typedef may be used for extern struct.(using sturuct in other file.)