Introduction

Related material. The GATE 2027 CS syllabus does not name function pointers or declaration reading separately. They are included because they are part of the syllabus item "Programming in C".

In C a pointer can hold the address of a function as well as of an object. Calling through such a pointer lets a program choose at run time which function to run: a sorting routine takes the comparison as a parameter, a calculator dispatches on an operator through a table of functions, an event system stores callbacks. The standard library's qsort, bsearch and signal all take function pointers.

Declarations of these types, and of pointers to arrays and arrays of pointers, look intimidating: int (*(*f)(void))[4]. They follow one consistent rule, though, and questions that ask "what is x?" can be answered mechanically with it. This note covers function pointers and their use, the declaration-reading rule, and typedef as the tool for writing such types readably.

Conventions: C11, hosted; outputs are for gcc on x86-64 Linux (LP64: pointers are 8 bytes, int is 4 bytes). Every declaration decoded in this note can be checked by the compiler with C11 _Generic against the same type built step by step with typedefs.

Prerequisites

Core concepts

Declaring and calling through a function pointer

C
int sq(int x) { return x * x; }

int (*fp)(int);        /* fp: pointer to function (int) returning int */
fp = sq;               /* function name decays to a pointer; &sq is the same value */
int a = fp(5);         /* 25 */
int b = (*fp)(5);      /* 25: explicit dereference is optional */
  • The parentheses in int (*fp)(int) are essential: int *fp(int) declares a function returning int *.
  • A function designator (a function's name used in an expression) is converted to a pointer to the function, except as the operand of sizeof or unary &. So sq and &sq give the same pointer.
  • A function call needs an operand of pointer-to-function type. fp(5) uses fp directly; in (*fp)(5), *fp is a function designator that immediately converts back to a pointer. Hence (**fp)(5) also works.
  • Pointer arithmetic on function pointers is not defined (they do not point into arrays), and sizeof cannot be applied to a function type.

Function pointers as parameters (callbacks)

C
int apply(int (*f)(int), int v) { return f(v); }
/* apply(neg, 7) is -7; apply(sq, -3) is 9 */

A parameter declared with function type is adjusted to a pointer: int apply(int f(int), int v) means the same thing.

qsort. The library sorts any array given a comparison function that returns a negative, zero or positive int:

C
#include <stdlib.h>
int cmp_desc(const void *p, const void *q) {
    int a = *(const int *)p, b = *(const int *)q;
    return (a < b) - (a > b);          /* descending; avoids overflow of b - a */
}
int v[] = {3, 9, 1, 7};
qsort(v, 4, sizeof v[0], cmp_desc);    /* v becomes 9 7 3 1 */

Writing return b - a; can overflow for large values of opposite sign, which is undefined behaviour; the comparison form avoids it.

Arrays of function pointers (dispatch tables)

C
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

int (*ops[3])(int, int) = {add, sub, mul};   /* ops: array of 3 pointers to function (int, int) returning int */
/* ops[0](8, 2) is 10, ops[1](8, 2) is 6, ops[2](8, 2) is 16 */

A table replaces a switch on an operation code with a single indexed call.

Functions returning function pointers

C
int (*pick(char c))(int, int) {               /* pick: function (char) returning pointer to function (int, int) returning int */
    return c == '+' ? add : c == '-' ? sub : mul;
}
/* pick('-')(10, 4) is 6 */

How it works: reading any declaration

A declaration has a base type (int, char, struct node, ...) and a declarator around the identifier. The operators in a declarator are the same as in expressions and bind the same way: postfix [] (array of) and () (function returning) bind tighter than prefix * (pointer to). Parentheses override that, exactly as in expressions.

The right-left rule (often drawn as a "spiral"):

  1. Start at the identifier: "x is ...".
  2. Look right: read each [n] as "array of nn" and each (...) as "function taking ... returning", until you hit a ) or the end.
  3. Then look left: read each * as "pointer to", until you hit a ( or the start.
  4. If you stopped at a parenthesis, step outside it and repeat from step 2.
  5. Finish with the base type.
Reading a declaration inside out
Figure 1. Reading int (*(*f)(void))[4] with the right-left rule: start at f, go left to * (pointer to), leave the parentheses, go right to (void) (function returning), left to * (pointer to), leave the parentheses, right to [4] (array of 4), and finally int.
DeclarationReading
int *a[5];a: array of 5 pointers to int
int (*a)[5];a: pointer to array of 5 int
int *f(int);f: function (int) returning pointer to int
int (*f)(int);f: pointer to function (int) returning int
int (*a[3])(int);a: array of 3 pointers to functions (int) returning int
int (*(*f)(void))[4];f: pointer to function (void) returning pointer to array of 4 int
char (*(*x(void))[2])(void);x: function (void) returning pointer to array of 2 pointers to functions (void) returning char
void (*signal(int, void (*)(int)))(int);signal: function (int, pointer to function (int) returning void) returning pointer to function (int) returning void

Under LP64 sizeof gives 40 for int *a[5] (five 8-byte pointers), 8 for int (*a)[5] (one pointer), and 24 for int (*a[3])(int).

Illegal combinations. A function cannot return an array or a function, and an array cannot have functions as elements. So int f()[5], int f()() and int a[3]() are constraint violations; the legal versions put a pointer in between (int (*f())[5], int (*(*f)())(), int (*a[3])()).

Abstract declarators. In a cast or a prototype the identifier is omitted; find where it would go (the place where the reading can start) and apply the same rule: void (*)(int) is "pointer to function (int) returning void".

typedef makes complex types readable

C
typedef void handler(int);            /* handler: function type (int) returning void */
handler *signal(int sig, handler *h); /* the signal declaration, rewritten */

typedef int (*binop)(int, int);       /* binop: pointer to function (int, int) returning int */
binop ops2[3] = {add, sub, mul};      /* same type as ops above */

A typedef declaration reads exactly like a variable declaration; the identifier names the type the variable would have had.

Worked examples

All results below are for C11 with gcc on x86-64 Linux (LP64).

Example 1. fp(5) and (*fp)(5) both give 25; apply(neg, 7) gives −7-7; apply(sq, -3) gives 9.

Example 2. ops[0](8, 2), ops[1](8, 2), ops[2](8, 2) give 10, 6, 16; pick('-')(10, 4) gives 6.

Example 3. qsort with cmp_desc turns 3 9 1 7 into 9 7 3 1.

Example 4. With static int row[4]; and int (*getrow(void))[4] { return &row; }, the statement (*getrow())[2] = 42; sets row[2] to 42. The call returns a pointer to the whole array; * gives the array, and [2] indexes it.

Example 5 (decoding). char (*(*x(void))[2])(void): start at x; right: function (void) returning; hit ), go left: pointer to; leave the parentheses; right: array of 2; hit ), left: pointer to; leave; right: function (void) returning; finally char. K&R's original example is the same type written in pre-prototype style with an incomplete array: char (*(*x())[])().

Common declaration traps

int *f() versus int (*f)(). The first declares a function returning int *. The second declares a pointer to a function returning int. Parentheses around *f are required; without them the * binds to the return type. Reading declarations with the right-left rule above (K&R 5.12 discusses complicated declarations) resolves options that differ only by parentheses.

Arrays of function pointers. void (*handlers[4])(int) is an array of four pointers to functions taking int and returning void. Initialising it with {f0, f1, f2, f3} and calling handlers[i](x) is the usual jump-table pattern. Confusing this with void *handlers[4](int) is a constraint violation (C11 6.7.6.2p1): an array of functions is not allowed.

Compatibility. Two function pointer types are compatible when their return types and parameter types are compatible. Assigning a function pointer through void * is not guaranteed by ISO C (that conversion is a common extension). Prefer an explicit typedef for the function type. A function pointer may be converted to another function-pointer type and back, comparing equal to the original. Calling through the converted pointer is undefined unless that function type is compatible with the actual function. Keep these rules separate from object-pointer/void * conversions.

Important properties and observations

  • [] and () bind tighter than * in declarations, as in expressions.
  • int *f(int) is a function; int (*f)(int) is a pointer to one.
  • f, &f and *f all yield the same function pointer value when used in a call.
  • Call only through a pointer of the correct function type; mismatches are undefined behaviour.
  • Function pointers are not object pointers: no arithmetic, and ISO C gives no conversion to void *.
  • Use typedef for any function pointer type that appears more than once.

GATE examination perspective

  • "What does this declaration declare?" Apply the right-left rule; check the answer by asking whether the combination is legal.
  • Output of code calling through a function pointer or a table of function pointers.
  • Pointer to array versus array of pointers (int (*p)[5] versus int *p[5]), including sizeof and the effect of p + 1 (Arrays and Their Relation to Pointers).
  • Callbacks such as a comparison passed to a sort.

Summary

Sources

  1. ISO/IEC 9899:2011 (C11), committee draft N1570, (consulted 2026-11-09), Section 6.3.2.1p4 (function designator conversion), 6.3.2.3p8 (conversions between function pointer types; calling through an incompatible type is undefined), 6.5.1.1 (generic selection), 6.5.2.2p1 (the called expression has pointer-to-function type), 6.7.6 (declarators), 6.7.8 (type definitions), 7.14.1.1 (signal), 7.22.5.2 (qsort).
  2. B. W. Kernighan and D. M. Ritchie, The C Programming Language, 2nd ed., Prentice Hall, 1988: Section 5.11 (pointers to functions), Section 5.12 (complicated declarations).

Next steps