In the terminology of the C standard, any expression that has function type is a function designator. So, when used as an expression, the name of a function is a function designator.
There is nothing you can do with a function designator except take its address. You cannot add a number to a function designator, you cannot compare it to another function designator, and so on. Of course, you can call a function, but this is actually done by address, not by designator, as I will explain in a moment. Since there is nothing you can do with a function except take its address, C does this for you automatically. Per C 2018 6.3.2.1 4:
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".
The result of this is:
- In
&foo
, the &
takes the address of foo
, so &foo
is the address of foo
.
- In
foo
, the function designator is automatically converted to the address of the function, so foo
is &foo
.
- In
*foo
, the function designator is automatically converted to the address of the function. Then the *
operator converts this back to the function, which produces a function designator. Then the automatic conversion happens again, and the function designator is converted back to the address of the function, so the result of *foo
is &foo
.
When you call a function, using the function ( argument-list... )
notation, the function
must actually be a pointer to a function. Thus, you can call foo
using (&foo)(arguments...)
. The automatic conversion simplifies the syntax so you can write foo(arguments...)
, but the call expression actually requires the address of the function, not the function designator.
Incidentally:
- A proper
printf
specifier for size_t
values is %zx
, not %lx
.
- If you include
<stdint.h>
, it defines a type intended for converting pointers to integers, uintptr_t
. This is preferably to converting pointers to size_t
.