Basic tutorial on function Pointers in C language

  • 2020-05-10 18:29:48
  • OfStack

As the name implies, a function pointer is a pointer to a function. It's a pointer to a function. See the examples:

A)


char * (*fun1)(char * p1,char * p2);

B)


char * *fun2(char * p1,char * p2);

C)


char * fun3(char * p1,char * p2);

What do the three expressions above mean?

C) this is easy, fun3 is the function name, p1, p2 is the parameter, its type is char *, the return value of the function is char *.
The only difference between B and C is that the return value of the function is of type char**, which is a level 2 pointer.
Is A) fun1 a function name? Recall the first time we talked about array Pointers. It might be clearer to say that array Pointers are defined this way:


int (*)[10] p;

See how similar the expression A is! You get the idea. Instead of a function name, fun1 is a pointer to a function. This function takes two pointer types and the return value of the function is one pointer. Again, let's rewrite this expression by 1:


char * (*)(char * p1,char * p2) fun1;

Does that look better? Unfortunately, the compiler doesn't think so. ^_^.


Function pointer and 1 simple function

Let's start with a very simple "Hello World" function and see how to create a pointer to a function.


#include <stdio.h>
 
//  The function prototype 
void sayHello();
 
// function 
void sayHello(){
  printf("hello world\n");
}
 
// main A function call 
int main() {
  sayHello();
}

We defined a function called sayHello that returns no value and accepts no arguments. When we call it from the main function, it prints "hello world" to the screen. Very simple. Next, we'll rewrite 1 to main, and instead of calling sayHello directly, we'll call it using a function pointer.


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

The syntax for line 2, void (*sayHelloPtr)(), looks a bit strange, so let's take it step by step.

Here, the keyword void is used to say that we created a function pointer to a function that returns void (that is, no return value).
Just like any other pointer must have a name of 1, sayHelloPtr is used here as the name of the function pointer.
We use the * symbol to indicate that this is a pointer, which is no different from declaring a pointer to an integer or a character.
*sayHelloPtr parenthesis is required, otherwise, the above declaration becomes void *sayHelloPtr(), * will be combined with void first, and becomes a declaration of a normal function that returns a pointer to void. Therefore, it is critical that you do not forget to include parentheses when you declare a pointer to a function.
The argument list follows the pointer name, which in this case is 1 against empty parentheses () because there are no arguments.
Combining the above points, the meaning of void (*syaHelloPtr)() is very clear, which is a function pointer to a function that takes no arguments and returns no value.
In line 2 above, void (*sayHelloPtr)() = sayHello; , we assign the function name sayHello to our new function pointer. For now, think of it as a tag that represents the function's address and can be assigned to a function pointer. This is similar to the statement int *x = &myint; We assign the address of myint to a pointer to an integer. It's just that when we're thinking about functions, we don't need to add an ampersand. In short, the function name is its address. Then on line 3, we use the code '(*sayHelloPtr)(); · 'dereferenced and called function pointer.

After line 2 is declared, sayHelloPtr is the name of the function pointer, no different from any other pointer, capable of storing and assigning values.
We also dereferenced sayHelloPtr in the same way as any other pointer 1, by using the dereferenced * before the pointer, which is *sayHelloPtr in the code.
Again, we need to put parentheses around it, which is (*sayHelloPtr), otherwise it will not be treated as a pointer to a function. Therefore, remember to always put parentheses around the ends of statements and references.
The parenthesis operator is used for function calls in the C language and is placed in parentheses if there are arguments involved. This is similar for function Pointers, which are (*sayHelloPtr)() in the code.
This function does not return a value, so there is no need to assign it to any variable. Separately, this call is no different from sayHello().
Next, let's modify the function a little bit. You'll see the strange syntax of function Pointers, and the phenomenon of calling an assigned function pointer the same way you would call a normal function.


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

As before, we assign the sayHello function to the function pointer. But this time, we called it the same way we would call a normal function. I'll explain this later when we talk about function names, but for now we just need to know that (*syaHelloPtr)() and syaHelloPtr() are the same.

Function Pointers with arguments

Ok, so this time let's create a new function pointer. The function it points to still doesn't return any value, but it has arguments.


#include <stdio.h>
 
// The function prototype 
void subtractAndPrint(int x, int y);
 
// function 
void subtractAndPrint(int x, int y) {
  int z = x - y;
  printf("Simon says, the answer is: %d\n", z);
}
 
//main A function call 
int main() {
  void (*sapPtr)(int, int) = subtractAndPrint;
  (*sapPtr)(10, 2);
  sapPtr(10, 2);
}

As in previous 1, the code includes function prototypes, function implementations, and statements executed through function Pointers in main functions. The features in the prototype and implementation have changed. The previous sayHello function does not take any arguments. This time the function subtractAndPrint takes two int arguments. It subtracts the two parameters one time and outputs them to the screen.

In line 14, we create sapPtr as a pointer to the function '(*sapPtr)(int, int)', only to replace the empty parentheses with (int, int). And that's consistent with the characterization of the new function.
In line 15, the function is derecked and executed in exactly the same way as before, with the addition of two arguments in parentheses, which becomes (10, 2).
On line 16, we call the function pointer the same way we would call a normal function.


A function pointer with an argument and a return value

This time, we will change the subtractAndPrint function to a function called subtract, which will take the output to the screen as the return value.


#include <stdio.h>
 
//  The function prototype 
int subtract(int x, int y);
 
//  function 
int subtract(int x, int y) {
  return x - y;
}
 
// main A function call 
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);
}

This is very similar to the subtractAndPrint function, except that the subtract function only returns an integer, and the signature is of course not the same.

In line 13, we create the subtractPtr function pointer via int (*subtractPtr)(int, int). The only difference from the previous example is that void is replaced by int to represent the return value. This is consistent with the characteristic standard of subtract function.
In line 15, the function pointer is dereferred and executed without any difference from calling subtractAndPrint, except that the return value is assigned to y.
On line 16, we output the return value to the screen.
In lines 18 to 19, we call the function pointer the same way we would call a normal function, and output the result.
It's the same as before, we just added the return value. Let's look at another slightly more complicated example -- passing a function pointer as an argument to another function.

Pass a function pointer as an argument

We've seen all sorts of situations where a function pointer is declared and executed, whether it takes an argument or whether it returns a value. Next we use a function pointer to execute different functions based on different inputs.


char * *fun2(char * p1,char * p2);
0

So let's go step by step.

We have two functions with the same signature, add and subtract, which both return an integer and take two integers as arguments.
In line 6, we define the functions int domath(int (*mathop)(int, int), int x, int y). Its first parameter, int (*mathop)(int, int), is a pointer to a function that returns an integer and accepts two integers as arguments. This is the syntax we've seen before, and it's not any different. Its last two integer parameters are used as simple inputs. So, this is a function that takes a pointer to a function and two integers as arguments.
Lines 19 through 21, the domath function passes its last two integer arguments to the function pointer and calls it. Of course, you could call it like this. mathop (x y);
Lines 27 to 31 show code we haven't seen before. We called the domath function with the function name as an argument. As I said before, the function name is the address of the function and can be used instead of a function pointer.
The main function calls the domath function twice, once using add, once using subtract, and outputs the result twice.

Function name and address

Since we have a prior engagement, let's end with the function name and address. A function name (or label) is converted to a pointer itself. This indicates that the function name can be used where the function pointer is required as input. This also results in some seemingly bad code running correctly. Take a look at this example.


char * *fun2(char * p1,char * p2);
1

This is a simple example. Run this code and you'll see that every function pointer executes, but you'll get a warning about character conversion. However, these function Pointers work fine.

On line 15, add, as the function name, returns the address of the function, which is implicitly converted to a function pointer. As I mentioned earlier, you can use the function name where the function pointer is required as input.
In line 16, the dereference is applied to add before *add, which returns the function at this address. Then, as with function name 1, it is implicitly converted to a function pointer.
On line 17, before taking the address character and applying it to add, &add, returns the address of this function, and then you get another function pointer.
On lines 18 to 19, add keeps de-referencing itself, keeps returning function names, and is converted to function Pointers. At the end of the day, the result is the same as the function name.
Obviously, this code is not good instance code. Here's what we learned: with 1, the function name is implicitly converted to a pointer, just as the array name is implicitly converted to pointer 1 when passed as an argument. You can use the function name anywhere a function pointer is required as input. Its 2, dereference * and fetch address & are mostly redundant before function names.


Related articles: