That is to say pointer to function, then in this way, just as a parameter can receive a reference to an object, the parameter can receive a reference to a function.
So C has what’s called high-order function or of first class. We treat functions as if they were data.
A function is naturally already a reference, after all the access to it is given by the address of the beginning of the code written in this function, analogous to a vector that has a pointer to the first element of it. This goes for even static functions, but in this case the address is associated to an existing identifier in the code and is called it directly, not have much to do with it.
But it is possible to store this address in a variable, or even pass as an argument to a function that just expects a type that is a function. It is, the very function, is not to call a function in the argument and pass the result of it, you pass it. Of course, it is said to pass the function without copying its code from one place to another, only the address is copied.
So just like an integer vector you can have a parameter more or less like int * x
when it is a function that takes an integer and returns another integer it would be something like this int (*x)(int)
, the first int
indicates that the function to be received should return a int
, it will be stored in a variable called x
and will receive as an argument int
, as demonstrated by the latter int
.
I discovered now that I could use the syntax without the pointer, when I learned I could only use it like this and I still need to research if this is standard, but I believe so by the source.
typedef
Obviously a function signature is a type of the parameter variable, being a type it is possible to create a typedef
to it and make the syntax more enjoyable.
It does not need to be only in parameter, but it is where it fits best. It could be a member of a struct
, has interesting cases for this, and is a way to simulate dynamic polymorphism.
Examples:
#include <stdio.h>
int func( int (*x)(int,int) ) { return x(20, 5); }
int func2( int x(int,int) ) { return x(20, 5); }
int func3( int(fn)() ) { return fn(); }
int func4( int(*fn)() ) { return fn(); }
typedef int cellulae_func(int, int);
int tabula(cellulae_func *cell, int lat, int alt) { return cell(lat, alt); }
int soma(int x, int y) { return x + y; }
int sub(int x, int y) { return x - y; }
int teste() { return 42; }
int main(void) {
printf("%d\n", tabula(soma, 20, 5));
printf("%d\n", tabula(sub, 20, 5));
printf("%d\n", func(soma));
printf("%d\n", func2(soma));
printf("%d\n", func3(teste));
printf("%d\n", func4(teste));
printf("%d\n", func4(soma)); //funciona, mas está errado
int (*funcs[2])(int x, int y);
funcs[0] = soma;
funcs[1] = sub;
printf("%d\n", func(funcs[0]));
}
Behold working in the ideone. And in the repl it.. Also put on the Github for future reference.
Note that I pass the static names of the functions, and since these names are actually pointers, I’m passing their address. On the other hand the variable that receives this pointer is used to call the function using the syntax of the parentheses. It cannot be used correctly otherwise, although old compilers or with wild configurations allow to do damage, and of course, with a cast everything is allowed, even if the result is bad.
The ideal is to use functions with signatures compatible, it is undefined behavior to use an inadequate, as demonstrated in one of the lines.
The two functions with name x
have the same signature, which is compatible with both the soma
as to sub
defined below. The typedef
is compatible with the same signature. The names could be different, because at the bottom this is just the variable name. The ones with the name fn
have another signature.
Use
The advantage of having this is that you can dynamically configure what you run in a given context. Which is the one about polymorphism (at least one form of it). And it is a substitute for the if
in many cases, since the functions can be placed in a vector or other data structure, as shown above in the example. So whenever any action need configured on demand, as needed at the moment, this type of function is interesting.
It’s used a lot as callback, where you call an algorithm that at a certain point needs to execute something that it does not yet know what it is, that is, it responds to something configurable by the caller. So think about handlers, Events.
C himself has algorithms waiting for a callback, for example the qsort()
where you say how it should perform the data comparison to sort the way you want, so picking up the specific data, deciding whether to do something extra operation with the data, such as giving a upper, whether it will be increasing or decreasing.
Creativity allows to make diverse compositions, could for example create a dictionary with (pointers to) the functions as its values and according to something typed by the user or getting the information otherwise at runtime call the appropriate function, which is much like what a language with enough dynamism and having a eval()
allows to make.
If it’s static this could be a jump table.
An application like the flame Lazy Evaluation, where you define what you want to do, but do not want it to be done at that time, then you "pass the code" somewhere and at the proper time the code is called.
Limiting
C has no syntax for lambda that could declare the function in the location it needs. Something like this:
printf("%d\n", tabula((int x, int y) => x + y, 20, 5));
C also doesn’t have its own capture mechanism of several of the current scope to load together with the function passed as parameter, something that other languages have, but nothing prevents you from manually creating this capability, it’s just not convenient.
Example
The most obvious example of use is with classification algorithms where you need to tell which is the criterion of which is lower than the other, even to decide the exact key or whether the order should be ascending or descending. Hence the function qsort()
C standard already takes a function as parameter. In C++ this is much more efficient.
#include <stdio.h>
#include <stdlib.h>
int compare(const void* left, const void* right) { return (*(int*)right - *(int*)left); }
int main() {
int (*cmp) (const void* , const void*) = &compare;
int array[] = {1, 8, 0, 4, 6, 5, 1, 6, 9, 7};
qsort(array, 10, sizeof(int), cmp);
for (int i = 0; i < 10; i++) printf("%d ", array[i]);
}
Behold working in the ideone. And in the repl it.. Also put on the Github for future reference.
Exercise: this generates a stable classification? The question is a trick.
C++
This should not be used in C++, except for compatibility, it has long had libraries that handle it better, and since 2011 has Amble and closures. And even some specific uses C++ has better solution.
Assembly
See how it looks after compiled by GCC (other compilers are theoretically less efficient):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
pop rbp
ret
func:
push rbp
mov rbp, rsp
mov esi, 5
mov edi, 20
call soma
pop rbp
ret
func2:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov esi, 5
mov edi, 20
call rax
leave
ret
tabula:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov DWORD PTR [rbp-12], esi
mov DWORD PTR [rbp-16], edx
mov ecx, DWORD PTR [rbp-16]
mov edx, DWORD PTR [rbp-12]
mov rax, QWORD PTR [rbp-8]
mov esi, ecx
mov edi, edx
call rax
leave
ret
main:
push rbp
mov rbp, rsp
mov edx, 5
mov esi, 20
mov edi, OFFSET FLAT:soma
call tabula
mov eax, 0
call func
mov edi, OFFSET FLAT:soma
call func2
mov eax, 0
pop rbp
ret
See better visually in the Compiler Explorer.
It should be noted that the call is indirect and has more complex instructions. What is perhaps not so obvious is the extra access to memory and this can be slower and makes more difference than a few more instructions. And you can see that every definition of types and contracts is thrown away.
Do you have everyone’s syntax reference? There’s a case I don’t know about.
– Maniero
I saw this on a page quoting these prototypes, but it only had this. The first two functions have the doubt here stackOverflEN But I can’t figure it out anyway with those answers and I found it useful to put here.
– Fábio Morais
Using pointer notation in the function name or using the name directly makes no difference, and the compiler will interpret both ways.
– Isac
@Isac talking about the last correct function? That function came up on a job of mine from college and I didn’t quite understand what it was for, it will have some very important use?
– Fábio Morais
I mean all of them, which are two multiplied by four because of the different ratings. The goal is even to function as a callback (something very common in languages with asynchronous calls), although in C it is not usually used for the same purpose. Basically passes a function that will then be called. It is based on this principle that can implement polymorphism in pure C.
– Isac
Can’t you develop that into a better answer and/or quote something to read about it? It seems that really these functions are important in polymorphism, I’ve never heard of it and now I’m curious about it and how it works.
– Fábio Morais