It happens with a few implicit conversions. Indeed, per the C standard:
ISO/IEC 2011, section 6.3.2.1 Lvalues, arrays, and function designators, paragraph 4
A function designator is an expression that has function type. Except when it is the operand of the sizeof
operator or the unary &
operator, a function designator with type “function returning type” is converted to an expression that has type “pointer to function returning type”.
Consider the following code:
void func(void);
int main(void)
{
void (*ptr)(void) = func;
return 0;
}
Here, the function designator func
has the type “function returning void
” but is immediately converted to an expression that has type “pointer to function returning void
”. However, if you write
void (*ptr)(void) = &func;
then the function designator func
has the type “function returning void
” but the unary &
operator explicitly take the address of that function, eventually yielding the type “pointer to function returning void
”.
This is mentioned in the C standard:
ISO/IEC 2011, section 6.5.3.2 Address and indirection operators, paragraph 3
The unary &
operator yields the address of its operand. If the operand has type “type”, the result has type “pointer to type”.
In particular, dereferencing a function pointer is redundant. Per the C standard:
ISO/IEC 2011, section 6.5.2.2 Function calls, paragraph 1
The expression that denotes the called function shall have type “pointer to function returning void
” or returning a complete object type other than an array type. Most often, this is the result of converting an identifier that is a function designator.
ISO/IEC 2011, section 6.5.3.2 Address and indirection operators, paragraph 4
The unary *
operator denotes indirection. If the operand points to a function, the result is a function designator.
So when you write
ptr();
the function call is evaluated with no implicit conversion because ptr
is already a pointer to function. If you explicitly dereference it with
(*ptr)();
then the dereferencing yields the type “function returning void
” which is immediately converted back to the type “pointer to function returning void
” and the function call occurs. When writing an expression composed of x unary *
indirection operators such as
(****ptr)();
then you just repeat the implicit conversions x times.
It does make sense that calling functions involves function pointers. Before executing a function, a program pushes all of the parameters for the function onto the stack in the reverse order that they are documented. Then the program issues a call
instruction indicating which function it wishes to start. The call
instruction does two things:
- First it pushes the address of the next instruction, which is the return address, onto the stack.
- Then, it modifies the instruction pointer
%eip
to point to the start of the function.
Since calling a function does involve modifying an instruction pointer, which is a memory address, it makes sense that the compiler implicitly converts a function designator to a pointer to function.
Even though it may seems unrigorous to have these implicit conversions, it can be useful in C (unlike C++ which have namespaces)
to take advantage of the namespace defined by a struct identifier to encapsulate variables.
Consider the following code:
void create_person(void);
void update_person(void);
void delete_person(void);
struct Person {
void (*create)(void);
void (*update)(void);
void (*delete)(void);
};
static struct Person person = {
.create = &create_person,
.update = &update_person,
.delete = &delete_person,
};
int main(void)
{
person.create();
person.update();
person.delete();
return 0;
}
It is possible to hide the implementation of the library in other translation units and to choose to only expose the struct encapsulating the pointers to functions, to use them in place of the actual function designators.