Understanding typedefs for function pointers in C
Solution 1
Consider the signal()
function from the C standard:
extern void (*signal(int, void(*)(int)))(int);
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:
typedef void (*SignalHandler)(int signum);
then you can instead declare signal()
as:
extern SignalHandler signal(int signum, SignalHandler handler);
This means the same thing, but is usually regarded as somewhat easier to read. It is clearer that the function takes an int
and a SignalHandler
and returns a SignalHandler
.
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:
(*functionpointer)(arg1, arg2, ...);
Modern syntax uses just:
functionpointer(arg1, arg2, ...);
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:
I have seen this explanation before. And then, as is the case now, I think what I didn't get was the connection between the two statements:
extern void (*signal(int, void()(int)))(int); /*and*/ typedef void (*SignalHandler)(int signum); extern SignalHandler signal(int signum, SignalHandler handler);
Or, what I want to ask is, what is the underlying concept that one can use to come up with the second version you have? What is the fundamental that connects "SignalHandler" and the first typedef? I think what needs to be explained here is what is typedef is actually doing here.
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 is SignalHandler
, and its type is:
a pointer to a function that takes an integer as an argument and returns nothing.
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:
type (*function)(argtypes);
After creating the signal handler type, I can use it to declare variables and so on. For example:
static void alarm_catcher(int signum)
{
fprintf(stderr, "%s() called (%d)\n", __func__, signum);
}
static void signal_catcher(int signum)
{
fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum);
exit(1);
}
static struct Handlers
{
int signum;
SignalHandler handler;
} handler[] =
{
{ SIGALRM, alarm_catcher },
{ SIGINT, signal_catcher },
{ SIGQUIT, signal_catcher },
};
int main(void)
{
size_t num_handlers = sizeof(handler) / sizeof(handler[0]);
size_t i;
for (i = 0; i < num_handlers; i++)
{
SignalHandler old_handler = signal(handler[i].signum, SIG_IGN);
if (old_handler != SIG_IGN)
old_handler = signal(handler[i].signum, handler[i].handler);
assert(old_handler == SIG_IGN);
}
...continue with ordinary processing...
return(EXIT_SUCCESS);
}
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 calling fprintf()
. I also print out the signal number that was received. In the alarm_handler()
function, the value will always be SIGALRM
as that is the only signal that it is a handler for, but signal_handler()
might get SIGINT
or SIGQUIT
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
and SIGTERM
too and about whether they are defined (#ifdef
conditional compilation), but that just complicates things. I'd also probably use POSIX sigaction()
instead of signal()
, 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 calls signal()
to find out whether the process is currently ignoring the signal, and while doing so, installs SIG_IGN
as the handler, which ensures that the signal stays ignored. If the signal was not previously being ignored, it then calls signal()
again, this time to install the preferred signal handler. (The other value is presumably SIG_DFL
, the default signal handler for the signal.) Because the first call to 'signal()' set the handler to SIG_IGN
and signal()
returns the previous error handler, the value of old
after the if
statement must be SIG_IGN
- hence the assertion. (Well, it could be SIG_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 see alarm_handler(1)
, you can consider that alarm_handler
is a pointer to the function and therefore alarm_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:
extern SignalHandler signal(int signum, SignalHandler handler);
If we changed the function name and the type like this:
extern double function(int num1, double num2);
you would have no problem interpreting this as a function that takes an int
and a double
as arguments and returns a double
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
, the signal()
function takes a SignalHandler
as its second argument, and it returns one as its result.
The mechanics by which that can also be treated as:
extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
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:
type var;
then when you write var
it represents a value of the given type
. For example:
int i; // i is an int
int *ip; // *ip is an int, so ip is a pointer to an integer
int abs(int val); // abs(-1) is an int, so abs is a (pointer to a)
// function returning an int and taking an int argument
In the standard, typedef
is treated as a storage class in the grammar, rather like static
and extern
are storage classes.
typedef void (*SignalHandler)(int signum);
means that when you see a variable of type SignalHandler
(say alarm_handler) invoked as:
(*alarm_handler)(-1);
the result has type void
- there is no result. And (*alarm_handler)(-1);
is an invocation of alarm_handler()
with argument -1
.
So, if we declared:
extern SignalHandler alt_signal(void);
it means that:
(*alt_signal)();
represents a void value. And therefore:
extern void (*alt_signal(void))(int signum);
is equivalent. Now, signal()
is more complex because it not only returns a SignalHandler
, it also accepts both an int and a SignalHandler
as arguments:
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
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).
Solution 2
A function pointer is like any other pointer, but it points to the address of a function instead of the address of data (on heap or stack). Like any pointer, it needs to be typed correctly. Functions are defined by their return value and the types of parameters they accept. So in order to fully describe a function, you must include its return value and the type of each parameter is accepts. When you typedef such a definition, you give it a 'friendly name' which makes it easier to create and reference pointers using that definition.
So for example assume you have a function:
float doMultiplication (float num1, float num2 ) {
return num1 * num2; }
then the following typedef:
typedef float(*pt2Func)(float, float);
can be used to point to this doMulitplication
function. It is simply defining a pointer to a function which returns a float and takes two parameters, each of type float. This definition has the friendly name pt2Func
. Note that pt2Func
can point to ANY function which returns a float and takes in 2 floats.
So you can create a pointer which points to the doMultiplication function as follows:
pt2Func *myFnPtr = &doMultiplication;
and you can invoke the function using this pointer as follows:
float result = (*myFnPtr)(2.0, 5.1);
This makes good reading: http://www.newty.de/fpt/index.html
Solution 3
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:
typedef int (*FUNC_TYPE_1)(void);
typedef double (*FUNC_TYPE_2)(void);
typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2);
rather than:
typedef int (*(*FUNC_TYPE_3)(double (*)(void)))(void);
cdecl
can help you out with this stuff:
cdecl> explain int (*FUNC_TYPE_1)(void)
declare FUNC_TYPE_1 as pointer to function (void) returning int
cdecl> explain double (*FUNC_TYPE_2)(void)
declare FUNC_TYPE_2 as pointer to function (void) returning double
cdecl> declare FUNC_TYPE_3 as pointer to function (pointer to function (void) returning double) returning pointer to function (void) returning int
int (*(*FUNC_TYPE_3)(double (*)(void )))(void )
And is (in fact) exactly how I generated that crazy mess above.
Solution 4
A very easy way to understand typedef of function pointer:
int add(int a, int b)
{
return (a+b);
}
typedef int (*add_integer)(int, int); //declaration of function pointer
int main()
{
add_integer addition = add; //typedef assigns a new variable i.e. "addition" to original function "add"
int c = addition(11, 11); //calling function via new variable
printf("%d",c);
return 0;
}
Solution 5
int add(int a, int b)
{
return (a+b);
}
int minus(int a, int b)
{
return (a-b);
}
typedef int (*math_func)(int, int); //declaration of function pointer
int main()
{
math_func addition = add; //typedef assigns a new variable i.e. "addition" to original function "add"
math_func substract = minus; //typedef assigns a new variable i.e. "substract" to original function "minus"
int c = addition(11, 11); //calling function via new variable
printf("%d\n",c);
c = substract(11, 5); //calling function via new variable
printf("%d",c);
return 0;
}
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.)
Related videos on Youtube
Comments
-
nbro almost 3 years
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!
-
Artelius over 14 yearsCan you provide some examples?
-
dave4420 over 14 yearsDo you not mean typedefs for function pointers, instead of macros for function pointers? I've seen the former but not the latter.
-
jww over 7 years
-
-
Admin over 14 yearspsychotik, thanks! That was helpful. The link to the function pointers web page is really helpful. Reading it now.
-
Admin over 14 yearsHi Carl, that was a very insightful example and explanation. Also, thanks for showing the using of cdecl. Much appreciated.
-
Admin over 14 years... However, that newty.de link does not appear to talk about typedefs at all :( So even though that link is great, but the responses in this thread about typedefs are invaluable!
-
Admin over 14 yearsI have seen this explanation before. And then, as is the case now, I think what I did't get was the connection between the two statements: extern void (signal(int, void()(int)))(int);/*and*/ typedef void (*SignalHandler)(int signum); extern SignalHandler signal(int signum, SignalHandler handler); Or, what I want to ask is, what is the underlying concept that one can use to come up with the second version you have? What is the fundamental that connects "SignalHandler" and the first typedef? I think what needs to be explained here is what is typedef is actually doing here. Thx
-
Jonathan Leffler over 14 years@psychotik: the Wikipedia article mentions typedefs and pointers to functions, but does not illustrate them at all. Maybe I should put some of this answer into the Wikipedia article :D
-
Admin over 14 yearsHi Jonathan, great reply! I use C and C++ to implement mathematical algorithm usually but never for programming an OS or other low level system related stuff. If this were known, perhaps it would have saved some typing on your part. The meat of your reply, for me, was when you started to clarify the "define" as a storage class in the grammar. I am familiar only with the usual typedefs. Even though I am familiar with ptrs to funcs, my problem was with the exact syntax of typedefs for func ptrs. You summarized well in your last sentences. The syntax needs some getting used to. Regards.
-
toto over 14 yearsGreat answer, I'm glad I came back to this thread. I don't think I understand everything, but one day I will. This is why I like SO. Thank you.
-
wildplasser over 11 yearsJust to pick a nit: it is not safe to call printf() and friends inside a signal handler; printf() is not re-entrant (basically because it can call malloc(), which is not reentrant)
-
nonsensickle about 11 yearsI believe that this link, c-pointer.blogspot.co.nz/2009/11/…, would help anyone needing to understand your explanation.
-
fredoverflow over 10 yearsThe distinction between
a()
and(*a)()
for calling functions directly or via function pointers does not actually exist. It is perfectly valid to define a functionvoid f(){}
and then call it via(*f)();
or even(***f)();
-
Jonathan Leffler over 10 years@FredOverflow: syntactically legal, yes; but anyone who used one of the forms you suggest for a regular function name should be hung, drawn, and made to code in Visual Basic for the rest of their life. And anyone using the triple star notation except to demonstrate that it is legal should be likewise condemned.
-
Jack almost 10 yearsIs there cdecl for windows?
-
Carl Norum almost 10 years@Jack, I'm sure you can build it, yes.
-
smwikipedia over 9 yearsThe
extern void (*signal(int, void(*)(int)))(int);
means thesignal(int, void(*)(int))
function will return a function pointer tovoid f(int)
. When you want to specify a function pointer as the return value, the syntax gets complicated. You have to place the return value type to the left and the argument list to the right, while it is the middle that you are defining. And in this case, thesignal()
function itself takes a function pointer as its parameter, which complicates things even more. Good news is, if you can read this one, the Force is already with you. :). -
zaknotzach almost 9 yearsThere is also cdecl.org which provides the same sort of capability but online. Useful for us Windows developers.
-
Tamilselvan almost 9 yearsYou might wanted to do
pt2Func myFnPtr = &doMultiplication;
instead ofpt2Func *myFnPtr = &doMultiplication;
asmyFnPtr
is already a pointer. -
nbro about 8 yearsYou can avoid this comment "So, what have we done here - apart from omit 4 standard headers that would be needed to make the code compile cleanly?" and simply include the headers so that the code compiles then.
-
AlphaGoku about 8 yearsdeclaring pt2Func *myFnPtr = &doMultiplication; instead of pt2Func myFnPtr = &doMultiplication; throws a warning.
-
Jonathan Leffler over 7 years@nbro: The
...continue with ordinary processing...
section still wouldn't compile with the headers present, so I see no urgency about including them. If the code compiled without other changes, I'd do as you suggest; since the code doesn't, I see no need to include them, though there'd be no harm in doing so either. JFTR, the headers are<assert.h>
,<signal.h>
,<stdio.h>
,<stdlib.h>
. -
Dustin Biser about 7 years@Tamilselvan is correct.
myFunPtr
is already a function pointer so usept2Func myFnPtr = &doMultiplication;
-
Aconcagua over 6 yearsJust wondering - you prefer the old-school dereferencing of a function pointer, but then just skip the old-school way of getting it (
{ SIGALRM,
&alarm_catcher}
)??? -
Jonathan Leffler over 6 yearsWhat's old school about using
&
in front of a function name? It's totally unnecessary; pointless, even. And definitely not "old school". Old school uses a function name plain and simple. -
remcycles about 4 years+1 for "
typedef
is treated as a storage class in the grammar, rather likestatic
andextern
". I never picked up on that unituitive bit of design, and seeing it pointed out is very helpful. In essence, you are declaring a variable, but the variable's name gets used as a new type name instead of actually creating a variable. Since many typedefs are of the formtypedef int new_type_t
, it's easy to assume the semantics are justtypedef some_type new_name
, even with function pointer types. Now it's easier to see why function pointer typedefs still are "middle out". -
Kumaravel Rajan almost 4 yearsThis was great. Thanks for this!
-
LocalHost over 3 years@DustinBiser I think
ptr2Func myFnPtr = doMultiplication
will also do good. The&
is not really required. -
Dutchmanjonny over 3 yearsI've been looking for this magically tool for years after first stumbling across it - glad to have randomly stumbled across this answer to refind it!