Industrial Training

Pointer to Structures



This post is very detailed because I am attempting to create a mental model to help beginners understand the syntax and basics of function pointers. If you are ok with detail happy reading. Function pointers are an interesting and powerful tool but their syntax can be a little confusing. This post will going into C function pointers from the basics to simple usage to some quirks about function names and addresses. In the end it will give you an easy way to think about function pointers so their usage is more clear.

A Simple Function and Function Pointer

Let’s start with a very simple function to print out the message hello world and see how we can create a function pointer from there.


#include ;
 
// function prototype
void sayHello();
 
// function implementation
void sayHello() {
  printf("hello world");
}
 
// calling from main
int main() {
  sayHello();
}

Here we have a function called sayHello along with its function prototype. This function returns nothing (void) and doesn’t take any parameters. We call the function from main and it prints out “hello world”. Pretty simple. Now lets convert main to use a function pointer instead of calling the function directly.

int main() {
  void (*sayHelloPtr)() = sayHello;
  (*sayHelloPtr)();
}

The syntax void (*sayHelloPtr)() on line 2 may look a little weird so let’s step by step it.

We are creating a function pointer to a function that returns nothing (void) so the return type is void. That is the void keyword. We have the pointer name sayHelloPtr. This is similar to creating any other pointer it has to have a name. We use the * notation to signify that it is a pointer. This is no different than declaring an int pointer or a char pointer. We must have parentheses around the pointer (*sayHelloPrt). If we don’t have parentheses it is seen as void *sayHelloPtr which is a void pointer instead of a pointer to a void function. This is a key point, function pointers must have parentheses around them. We have the parameter list which in this case there isn’t one so it is just empty parentheses (*sayHelloPrt)(). Putting it all together we get void (*sayHelloPtr)(), a pointer to a function that returns void and takes no parameters. On line 2 above we are assigning the sayHello function name to our newly created function pointer like this void (*sayHelloPtr)() = sayHello. We will go into more detail about function names later, but for now understand that a function name (label) is the address of the function and it can be assigned to a function pointer. This is similar to int *x = &myint where we assign the address of myint to an int pointer. Only in the case of a function, the address-of the function is the function name and we dont’ need the address-of operator. Simply put, the function name is the address-of the function. On line 3 we dereference and call our function pointer like this (*sayHelloPtr)().

Once created on line 2 sayHelloPtr is our function pointer name and can be treated just like any other pointer, assigned, stored. We dereference our sayHelloPtr pointer the same as we dereference any other pointer, by using the value-at-address (*) operator. This gives us *sayHelloPtr.

Again we must have parentheses around the pointer (*sayHelloPrt). If we don’t it isn’t a function pointer. We must have parentheses when creating a function pointer and when dereferencing it.

The () operator is used to call a function in C. It is no different on a function pointer. If we had a parameter list there would be values in the parentheses similar to any other function call. This gives us (*sayHelloPrt)(). This function has no return value so there is no need to assign its return to any variable. The function call can standalone similar to sayHello().

Now that we have show the weird syntax understand that often function pointers are just treated and called as regular functions after being assigned. To modify our previous example.

int main() {
  void (*sayHelloPtr)() = sayHello;
  sayHelloPtr();
}

As before we assign the sayHello function to our function pointer, but now we call the function pointer just like we would call a regular function. We will get into function names later which will show why this works but for now understand that calling a function pointer with full syntax (*sayHelloPtr)() is the same as calling the function pointer as a regular function sayHelloPtr().


A Function Pointer with Parameters

Now lets create a function pointer that still doesn’t return anything (void) but now has parameters.

#include 
 
// function prototype
void subtractAndPrint(int x, int y);
 
// function implementation
void subtractAndPrint(int x, int y) {
  int z = x - y;
  printf("Simon says, the answer is: %d\n", z);
}
 
// calling from main
int main() {
  void (*sapPtr)(int, int) = subtractAndPrint;
  (*sapPtr)(10, 2);
  sapPtr(10, 2);
}

As before we have our function prototype, our function implementation and the executing of the fuction from main using a function pointer. The signature of both the prototype and its implementation have changed. Where before our sayHello function didn’t have parameters, the subtractAndPrint function takes two parameters, both integers, subtracts one from the other and prints the result.

We create our sapPtr function pointer on line 14 with void (*sapPtr)(int, int). The only difference from before is now instead of empty parentheses on the end when creating the function we have (int, int) which matches the signature of our new function. On line 15 when dereferecing and executing the function, everything is the same as when we called our sayHello function except now we have (10, 2) on the end passing parameters.
On line 16 we show executing the function pointer as a regular function.



A Function Pointer with Parameters and Return Value

Let’s change our subtractAndPrint function to be called subtract and to return the result instead of printing it.

#include 
 
// function prototype
int subtract(int x, int y);
 
// function implementation
int subtract(int x, int y) {
  return x - y;
}
 
// calling from main
int main() {
  int (*subtractPtr)(int, int) = subtract;
 
  int y = (*subtractPtr)(10, 2);
  printf("Subtract gives: %d\n", y);
 
  int z = subtractPtr(10, 2);
  printf("Subtract gives: %d\n", z);
}

Similar to the subtractAndPrint function except now the subtract function returns an int. The prototype and function signatures have changed as would be expected. We create our subtractPtr function pointer on line 13 with int (*subtractPtr)(int, int). The only difference from before is instead of void we have an int return value. This matches our subtract method signature. On line 15 when dereferencing and executing the function pointer, everything is the same as when we called our subtractAndPrint function except now we have int y = which assigns the return value of the function to y. On line 16 we print out the return value. On lines 18-19 we execute the function pointer as a regular function and print the results. Not much difference from before, we just added the int return value. Let’s move on to a little more complex example where we pass a function pointer into another function as a parameter.


Passing a Function Pointer as a Parameter

We have stepped through the main parts of the declaring and executing function pointers with and without parameters and return values. Now lets look at using a function pointer to execute different functions based on input.

#include 
 
// function prototypes
int add(int x, int y);
int subtract(int x, int y);
int domath(int (*mathop)(int, int), int x, int y);
 
// add x + y
int add(int x, int y) {
  return x + y;
}
 
// subtract x - y
int subtract(int x, int y) {
  return x - y;
}
 
// run the function pointer with inputs
int domath(int (*mathop)(int, int), int x, int y) {
  return (*mathop)(x, y);
}
 
// calling from main
int main() {
 
  // call math function with add
  int a = domath(add, 10, 2);
  printf("Add gives: %d\n", a);
 
  // call math function with subtract
  int b = domath(subtract, 10, 2);
  printf("Subtract gives: %d\n", b);
}

Function Names and Addresses

Let’s wrap up by talking a bit about function names and addresses as promised. A function name (label) is converted to a pointer to itself. This means that function names can be used where function pointers are required as input. It also leads to some very funky looking code that actually works. Take a look at some examples.

#include 
 
// function prototypes
void add(char *name, int x, int y);
 
// add x + y
void add(char *name, int x, int y) {
  printf("%s gives: %d\n", name, x + y);
}
 
// calling from main
int main() {
 
  // some funky function pointer assignment
  void (*add1Ptr)(char*, int, int) = add;
  void (*add2Ptr)(char*, int, int) = *add;
  void (*add3Ptr)(char*, int, int) = &add;
  void (*add4Ptr)(char*, int, int) = **add;
  void (*add5Ptr)(char*, int, int) = ***add;
 
  // execution still works
  (*add1Ptr)("add1Ptr", 10, 2);
  (*add2Ptr)("add2Ptr", 10, 2);
  (*add3Ptr)("add3Ptr", 10, 2);
  (*add4Ptr)("add4Ptr", 10, 2);
  (*add5Ptr)("add5Ptr", 10, 2);
 
  // this works too
  add1Ptr("add1PtrFunc", 10, 2);
  add2Ptr("add2PtrFunc", 10, 2);
  add3Ptr("add3PtrFunc", 10, 2);
  add4Ptr("add4PtrFunc", 10, 2);
  add5Ptr("add5PtrFunc", 10, 2);
}
Hi I am Pluto.