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.