void error(int i); void (*p)(int); p=&error; (*p)(1); 

To begin with, I will try to read the second line, that is, p is a pointer to a function that returns values ​​of type void. Why I read this and in particular the "void type" because I used the technique from here . But what are the rules for all this?
I roughly understand it as:

  1. the implementation of the function (just for example, and the announcement was also there, but it did not write here);
  2. already read it above, I will add only that the grouping brackets are necessary in order that there was a pointer to a function, and not the name of the function and its returning type void* . It is strange that after reading a single book on C ++, I did not meet there mention of the main type and derivative, and for this reason I was surprised by this record, what can we say about such int (*(*foo)())(); , it would be generally a dark forest, if not for that technique.
  3. Here the pointer "p" is assigned an address, but what is the address for the function prototype?
  4. The pointer is dereferenced and the function name error simply substituted in its place and (error)(1) obtained? That is, it is possible to wrap any function with grouping brackets and this does not change the essence.
    Such a record is needed only because it is required by pointers, that is, so that the pointer has the same type as the specified object?
  • 2
    The authors of the GO guide mocked plenty of descriptions of types. And there is a reason. 3. The address of the error function is substituted. If it is described in this translation unit, then the translator can do it, if not, then the link is external, and the linker substitutes the address. 4. Brackets - to eliminate ambiguity. (Wrongly expressed: the requirement of syntax. Otherwise, it turns out to take the value to the address p (1). Since the result is of type void, then it is impossible to take the value to its address). It is lazy to search, but the translator does not swear at p=error; and p(1); - alexlz
  • one
    3. P is assigned an implementation address. that is, we can assume that the address is stored here in the memory where the function code is located. And the fact that the parameters match is checked by the compiler. - KoVadim
  • one
    In newty.de/fpt/fpt.html#chapter2 these abbreviations are called 'short form' for p=error; and 'C short way' for p(1); - alexlz
  • 3
    GREAT REQUEST! Tags with ++ and c ++ are not identical. In one case, with - Russian, in the second - English. Please use the correct c ++ tag everywhere. - gecube
  • 2
    @mzarb, there (after the answer where you wrote a lot of comments) my comment limit was over. I'll try to answer here. IMHO basically you understand correctly. See gcc void p = (void *) puts, (* fp) () = p; fp ("call puts"); // it works // p ("call puts"); and this is not compiled, but in g ++ it is necessary void * p = (void *) puts, (* fp) (const char *) = (void ( ) (const char *)) p; Those. For the compiler important formalities. Especially for C ++. This is indeed correct (reduces the number of stupid mistakes). So look carefully at the @alexlz answer. - avp

2 answers 2

Good question. In general, the topic of describing function pointers is perhaps the most confusing in C and especially in C ++. The greatest problems arise with the correct for the compiler description of the function that returns the address of the function.

Practically it is necessary to understand that the pointer to the function is the address of the first machine command of the function body and its call is made by loading this address into the register and issuing the call instruction for the register, for example

 movq -16(%rbp), %rax // загрузка адреса функции call *%rax // вызов 

In practice, it is convenient (I agree that it is not entirely correct, but it really works) to describe the addresses of functions as void * and, if necessary, use a type conversion.

For example: (works in both C and C ++)

 // fu1.c вызов функций по адресу #include <stdio.h> // если первый аргумент не 0, то // возвращает второй аргумент (передаваемую функцию), // иначе определенную в fu2.c функцию extern void *getfu(int, int (*f)(int)); // возвращает массив определенных в fu2.c функций extern void **getvfu(); static int qq (int i) // передаем эту функцию как аргумент в getfu() { return printf("qq=%d\n",i); } int main (int ac, char *av[]) { int i, (*p)(int); void **v = getvfu(); // получим массив функций из fu2.c for (i = 0; v[i]; i++) ((int (*)(int))(v[i]))(i); p = (int (*)(int))(av[1] ? getfu(0,0) : getfu(1,qq)); int rc = p(10); return printf ("rc = %d\n",rc) < 0; } // fu2.c функции и их массив #include <stdio.h> static int fu (int i) { return printf ("fu=%d\n",i); } void * getfu (int fa, int (*f)(int)) { return (void *)(fa ? f : fu); } static int f1 (int i) { return printf ("f1: %d\n",i); } static int f2 (int i) { return printf ("f1: %d\n",i); } static int f3 (int i) { return printf ("f1: %d\n",i); } void ** getvfu() { static void *fv[] = {(void *)f1, (void *)f2, (void *)f3, (void *)fu, 0}; return fv; } 

We broadcast and run

 avp@avp-ubu1:~/hashcode$ g++ -c fu1.c fu2.c avp@avp-ubu1:~/hashcode$ g++ fu[12].o -o a++ avp@avp-ubu1:~/hashcode$ ./a++ 1 f1: 0 f1: 1 f1: 2 fu=3 fu=10 rc = 6 avp@avp-ubu1:~/hashcode$ gcc fu1.c fu2.c avp@avp-ubu1:~/hashcode$ ./a.out 1 f1: 0 f1: 1 f1: 2 fu=3 fu=10 rc = 6 avp@avp-ubu1:~/hashcode$ ./a.out f1: 0 f1: 1 f1: 2 fu=3 qq=10 rc = 6 avp@avp-ubu1:~/hashcode$ 

Perhaps this (not quite the correct solution) will be useful.

By the way, someone can write a really correct (preferably going and working) version for C and C ++.

  • Hmm, it seemed to me that function pointers are not reducible to void pointers. - VladD
  • one
    @mzrab, ((int ( ) (int)) (v [i])) (i); this is a function call from v [i]: (v [i]) take an address from the i-th element v [] is type void * (int ( ) (int)) is a cast of type void *, i.e. the pointer does not know what type of pointer (i.e., address) to the function, which takes an argument of type int and returns a result of type int Thus we get the address (pointer) of the desired type (with so-called compiler) (...) (i) ; the actual function call (from v [i]) with the parameter i In essence, this is a cast to the pointer int (* p) (int); as in the question. - avp
  • one
    rc = 6 is the result returned by printf() is the number of bytes printed. see man 3 printf. - avp
  • one
    @mzarb ((int (*)(int))(v[i]))(i) Cast v [i] to the type (int (*) (int)), i.e. pointer to an integer function of an integer and call this function with the parameter (i). - alexlz pm
  • one
    @mzarb added his answer. - alexlz

@avp Right is that so?

  static int f1 (int i) { return printf ("f1: %d\n",i); } static int f2 (int i) { return printf ("f1: %d\n",i); } static int f3 (int i) { return printf ("f1: %d\n",i); } static int fu (int i) { return printf ("fu=%d\n",i); } int (** getvfu())(int) { static int (*fv[])(int) = {f1, f2, f3, fu, 0}; return fv; } int main(int argc, char* argv[]) { int (**fv)(int) = getvfu(); fv[0](1); fv[1](2); fv[2](3); fv[3](4); return 0; } 

Accidentally stumbled upon another thing: Reading C type declarations


 там узнал что переменные не передаются, а присваиваются в месте между телом функции и сигнатурой, 

It's not entirely clear what you wrote. Traditionally (I admit that it is not always and everywhere) the parameters in the function in C are passed through the stack. There are different methods for passing parameters: "by value" (a value is passed, a function with it can do what it wants, in the calling program / function the value of the parameter does not change). In C, all parameters are transmitted exactly like this. There is a transfer method "by reference" - a reference to a variable (or a value is passed to the function / subroutine, and it can be funny if the function changes this value. One of the typical errors). The function is working with the variable-actual parameter through this link. The classic of the genre is FORTRAN. In C, there is no such thing, but it is possible to simulate such work by passing a pointer to a variable as a parameter. The pointer itself is passed "by value", and a function can do whatever it wants with a variable.

There are other methods, for example in Algol-60 was the method of passing parameters "by name". In most cases, I performed the same tasks as the method of passing parameters "by reference", but had some additional features (now some languages ​​have similar things - code block parameters, like Obj-c, perl, etc.). Implementation - thunk'i.

Signature has nothing to do with it. It serves only to check the correctness and addition of some default type conversions.

  • @alexlz: in the same box office cdecl.org - VladD
  • @alexlz, for sure !!! I think this answer will be useful to many for the correct description of functions that return functions. I can formulate this rule as follows: a description of the function itself, its name and parameters are enclosed in brackets and instead of a type we write an asterisk (or several, depending on the level of indirection), the type of the result of the returned function is written before the brackets, and its arguments are described after the brackets. Accordingly, for the prototype at the beginning we write, for example, extern and complete such a description ';' if we write the function itself, then we end it with the body in { ... } . - avp