Understanding typedefs for function pointers in C

297,759

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

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

Share:
297,759

Related videos on Youtube

nbro
Author by

nbro

don't believe the hype

Updated on June 29, 2021

Comments

  • nbro
    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
      Artelius over 14 years
      Can you provide some examples?
    • dave4420
      dave4420 over 14 years
      Do you not mean typedefs for function pointers, instead of macros for function pointers? I've seen the former but not the latter.
    • jww
      jww over 7 years
  • Admin
    Admin over 14 years
    psychotik, thanks! That was helpful. The link to the function pointers web page is really helpful. Reading it now.
  • Admin
    Admin over 14 years
    Hi Carl, that was a very insightful example and explanation. Also, thanks for showing the using of cdecl. Much appreciated.
  • Admin
    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
    Admin over 14 years
    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. Thx
  • Jonathan Leffler
    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
    Admin over 14 years
    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.
  • toto
    toto over 14 years
    Great 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
    wildplasser over 11 years
    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)
  • nonsensickle
    nonsensickle about 11 years
    I believe that this link, c-pointer.blogspot.co.nz/2009/11/…, would help anyone needing to understand your explanation.
  • fredoverflow
    fredoverflow over 10 years
    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)();
  • Jonathan Leffler
    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
    Jack almost 10 years
    Is there cdecl for windows?
  • Carl Norum
    Carl Norum almost 10 years
    @Jack, I'm sure you can build it, yes.
  • smwikipedia
    smwikipedia over 9 years
    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. :).
  • zaknotzach
    zaknotzach almost 9 years
    There is also cdecl.org which provides the same sort of capability but online. Useful for us Windows developers.
  • Tamilselvan
    Tamilselvan almost 9 years
    You might wanted to do pt2Func myFnPtr = &doMultiplication; instead of pt2Func *myFnPtr = &doMultiplication; as myFnPtr is already a pointer.
  • nbro
    nbro about 8 years
    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.
  • AlphaGoku
    AlphaGoku about 8 years
    declaring pt2Func *myFnPtr = &doMultiplication; instead of pt2Func myFnPtr = &doMultiplication; throws a warning.
  • Jonathan Leffler
    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
    Dustin Biser about 7 years
    @Tamilselvan is correct. myFunPtr is already a function pointer so use pt2Func myFnPtr = &doMultiplication;
  • Aconcagua
    Aconcagua over 6 years
    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})???
  • Jonathan Leffler
    Jonathan Leffler over 6 years
    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.
  • remcycles
    remcycles about 4 years
    +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".
  • Kumaravel Rajan
    Kumaravel Rajan almost 4 years
    This was great. Thanks for this!
  • LocalHost
    LocalHost over 3 years
    @DustinBiser I think ptr2Func myFnPtr = doMultiplication will also do good. The & is not really required.
  • Dutchmanjonny
    Dutchmanjonny over 3 years
    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!