Prototype functions in C/C++

Asked

Viewed 930 times

10

What kinds of functions are these? What can these prototypes do?

 /*1*/int func ( int (*x)(int,int) )


 /*2*/int func ( int x(int,int) ) 


 /*3*/int func1 ( int(fn)() )


 /*4*/int func2 ( int(*fn)() )

Is this type of function called pointer to function? How is it used and why is it used.

typedef int cellulae_func(int, int);

void tabula(cellulae_func *cell, int lat, int alt);
  • 1

    Do you have everyone’s syntax reference? There’s a case I don’t know about.

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

  • Using pointer notation in the function name or using the name directly makes no difference, and the compiler will interpret both ways.

  • @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?

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

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

Show 1 more comment

2 answers

9


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.

4

However I remembered a common situation where this is used and that probably also helps to contextualize, which is in an ordering.

Just as @Maniero already explained this are pointers to functions, and the name can be either with or without pointer notation that the compiler will treat equally.

So these two are the same:

 /*1*/int func ( int (*x)(int,int) )
 //                   ^--

 /*2*/int func ( int x(int,int) ) 
 //                  ^--

Imagine you have a Bubble Sort to sort an array of integers:

void bubble_sort(int arr[], int tamanho) {
    int i, j;
    for (i = 0; i < tamanho - 1; i++) {
        for (j = 0; j < tamanho - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp =  arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

But now I would like to be able to use other types without just being whole. How will you compare ? The comparison depends intrinsically on the type and even becomes worse when we consider types that the programmer can create with struct. Soon it is impossible for you to know beforehand how to compare.

Instead you get a function that knows how to compare two elements and returns:

  • < 0 when the first element is smaller
  • 0 when they’re equal
  • > 0 when the first element is larger

The very one qsort which exists in the standard library also gets a function to compare. See an example of an integer comparison function for the qsort:

int compare (const void * a, const void * b){
    return ( *(int*)a - *(int*)b );
}

And now when you call qsort passes the function as argument:

qsort (values, 6, sizeof(int), compare);
//                                ^--- aqui

So the qsort calls its function whenever it has to compare two elements in the array and sort them.

So in this example of Bubble Sort we can do the same, receive a function to compare each element. This has some implications in relation to the parameters of this bubble_sort:

  • The array must become one void* to point to any type in memory

  • It is also necessary to know how many bytes a type occupies in memory to know how many bytes we go forward whenever we want to go to the next element

  • It is also necessary to receive the function that knows to compare two elements

With this the new function signature can be:

void bubble_sort_generico(void *arr, int tamanho, int bytes_elem, int comparador(void*, void*) );
//                    ponteiro de função identico ao que tem na pergunta ----^

Now the implementation becomes more complex because it deals with void* and each advance in the array has to be done in x bytes corresponding to the amount of bytes each element has:

void bubble_sort_generico(void *arr, int tamanho, int bytes_elem, int comparador(void*, void*) ){
    int i, j, tamanho_bytes = tamanho * bytes_elem;
    for (i = 0; i < tamanho_bytes - bytes_elem; i += bytes_elem) {
        for (j = 0; j < tamanho_bytes - i - bytes_elem; j += bytes_elem) {
            void *ptr_elem1 = arr + j;
            void *ptr_elem2 = arr + j + bytes_elem;

            if (comparador(ptr_elem1, ptr_elem2) > 0){ //chamar o comparador
                char temp[1024];
                memcpy(temp, ptr_elem1, bytes_elem);
                memcpy(ptr_elem1, ptr_elem2, bytes_elem);
                memcpy(ptr_elem2, temp, bytes_elem);
            }
        }
    }
}

Each of the memcpys corresponds to the simple assignment previously made with int temp = arr[j];. As each element can have several bytes there is no way to make a simple assignment and so the solution is to copy byte by byte until you make up all the bytes that make an element. For simplicity I used the memcpy but could also be done manually with a for.

The instruction comparador(ptr_elem1, ptr_elem2) that’s inside the if is the one that calls the received function as a parameter to make the comparison, passing two pointers as arguments.

The comparator could be a copy of what I showed you qsort but adjusted to the types the function expects:

int comparador (void *a, void *b){
    return ( *(int*)a - *(int*)b );
}

Note that I have not used const void* for simplicity but would make more clear sense, as the comparison function is not supposed to change anything.

It is worth remembering that this implementation may be better in some ways, but I tried to keep it simple since the concept itself works for everything with void* has already tended to be complex enough. This was also the reason why I opted for one of the simplest sorting algorithms.

The call on main would be so:

int main() {
    int nums[] = {37, 2, 59, 1, 19, 3, 14};
    int tamanho = sizeof(nums) / sizeof(nums[0]);
    bubble_sort_generico(nums, tamanho, sizeof(int), comparador);

Now the difference is that you can also sort strings for example if you pass a function that can compare two strings:

int comparador_nomes(void * a, void* b){
    char **nome1 = (char**)a;
    char **nome2 = (char**)b;
    return strcmp(*nome1, *nome2);
}

int main() {
    char *nomes[] = {"joao", "filipa", "rita", "ana", "marcos"};
    int tamanho2 = sizeof(nomes) / sizeof(nomes[0]);
    bubble_sort_generico(nomes, tamanho2, sizeof(char*) , comparador_nomes);

Note that I compared the names based on strcmp.

See these two examples in Ideone

As a final note, if you look at the signature of qsort in some of the implementations, sees something similar to the examples he put forward:

typedef int (*__compar_d_fn_t) (const void *, const void *, void *);

void _quicksort (void *const pbase, size_t total_elems, size_t size, __compar_d_fn_t cmp, void *arg) {
//                                                                   ^-- função aqui

The difference is they made one typedef to be syntactically simpler to write the function.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.