Understanding typedefs for function pointers in C
Asked Answered
R

8

286

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!

Raouf answered 19/10, 2009 at 21:55 Comment(3)
Can you provide some examples?Tamartamara
Do you not mean typedefs for function pointers, instead of macros for function pointers? I've seen the former but not the latter.Darby
Also see How to declare an __stdcall function pointer.Anaemic
S
350

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).

Stealage answered 19/10, 2009 at 22:29 Comment(13)
I 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. ThxRaouf
@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 :DStealage
Hi 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.Raouf
Just 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)Belonging
I believe that this link, c-pointer.blogspot.co.nz/2009/11/…, would help anyone needing to understand your explanation.Madonia
The distinction between a() and (*a)() for calling functions directly or via function pointers does not actually exist. It is perfectly valid to define a function void f(){} and then call it via (*f)(); or even (***f)();Cade
@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.Stealage
The extern void (*signal(int, void(*)(int)))(int); means the signal(int, void(*)(int)) function will return a function pointer to void 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, the signal() 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. :).Middendorf
You 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.Ewold
@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>.Stealage
Just 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})???Henden
What'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.Stealage
+1 for "typedef is treated as a storage class in the grammar, rather like static and extern". 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 form typedef int new_type_t, it's easy to assume the semantics are just typedef some_type new_name, even with function pointer types. Now it's easier to see why function pointer typedefs still are "middle out".Amil
L
94

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

Lema answered 19/10, 2009 at 22:17 Comment(5)
... 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!Raouf
You might wanted to do pt2Func myFnPtr = &doMultiplication; instead of pt2Func *myFnPtr = &doMultiplication; as myFnPtr is already a pointer.Hang
declaring pt2Func *myFnPtr = &doMultiplication; instead of pt2Func myFnPtr = &doMultiplication; throws a warning.Zook
@Hang is correct. myFunPtr is already a function pointer so use pt2Func myFnPtr = &doMultiplication;Brokendown
@DustinBiser I think ptr2Func myFnPtr = doMultiplication will also do good. The & is not really required.Dartboard
M
38

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 typedefs 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.

Mallory answered 19/10, 2009 at 22:11 Comment(5)
Hi Carl, that was a very insightful example and explanation. Also, thanks for showing the using of cdecl. Much appreciated.Raouf
Is there cdecl for windows?Acarpous
@Jack, I'm sure you can build it, yes.Mallory
There is also cdecl.org which provides the same sort of capability but online. Useful for us Windows developers.Gaven
I'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!Phraseologist
G
37

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;
}
Grous answered 17/9, 2013 at 3:24 Comment(0)
N
13
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.)

Nonah answered 17/1, 2014 at 11:58 Comment(0)
P
12

Use typedef's to define more complicated types i.e function pointers

I will take the example of defining a state-machine in C

    typedef  int (*action_handler_t)(void *ctx, void *data);

now we have defined a type called action_handler that takes two pointers and returns a int

define your state-machine

    typedef struct
    {
      state_t curr_state;   /* Enum for the Current state */
      event_t event;  /* Enum for the event */
      state_t next_state;   /* Enum for the next state */
      action_handler_t event_handler; /* Function-pointer to the action */

     }state_element;

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

    int handle_event_a(void *fsm_ctx, void *in_msg );

    int handle_event_b(void *fsm_ctx, void *in_msg );

References:

Expert C programming by Linden

Polyp answered 21/1, 2018 at 10:42 Comment(0)
V
4

This is the simplest example of function pointers and function pointer arrays that I wrote as an exercise.

    typedef double (*pf)(double x);  /*this defines a type pf */

    double f1(double x) { return(x+x);}
    double f2(double x) { return(x*x);}

    pf pa[] = {f1, f2};


    main()
    {
        pf p;

        p = pa[0];
        printf("%f\n", p(3.0));
        p = pa[1];
        printf("%f\n", p(3.0));
    }
Vocabulary answered 12/2, 2015 at 18:45 Comment(0)
R
0

What about a macro? I've seen that with a typedef, I cannot cast something to a function pointer. So I made this small macro for it, which is able to do everything I've needed so far (no idea of things it can't be used on - I think it's basically a typedef, but with an optional parameter to make it usable on casts):

#define funcptr_t(sym_name) void (*sym_name)(void)

// Declare a function pointer-returning function, and declare a function pointer variable.
funcptr_t (randomFunction(funcptr_t (func_ptr_variable)));
// Cast a variable to a function pointer
(funcptr_t()) some_variable;

PS: I made this for generic usage, not sure if it could be modified for specific-functions use.

Roussel answered 11/10, 2022 at 23:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.