Why do function pointer definitions work with any number of ampersands '&' or asterisks '*'?
Asked Answered
D

5

253

Why do the following work?

void foo() {
    cout << "Foo to you too!\n";
};

int main() {
    void (*p1_foo)() = foo;
    void (*p2_foo)() = *foo;
    void (*p3_foo)() = &foo;
    void (*p4_foo)() = *&foo;
    void (*p5_foo)() = &*foo;
    void (*p6_foo)() = **foo;
    void (*p7_foo)() = **********************foo;

    (*p1_foo)();
    (*p2_foo)();
    (*p3_foo)();
    (*p4_foo)();
    (*p5_foo)();
    (*p6_foo)();
    (*p7_foo)();
}
Demerol answered 1/8, 2011 at 0:37 Comment(0)
L
253

There are a few pieces to this that allow all of these combinations of operators to work the same way.

The fundamental reason why all of these work is that a function (like foo) is implicitly convertible to a pointer to the function. This is why void (*p1_foo)() = foo; works: foo is implicitly converted into a pointer to itself and that pointer is assigned to p1_foo.

The unary &, when applied to a function, yields a pointer to the function, just like it yields the address of an object when it is applied to an object. For pointers to ordinary functions, it is always redundant because of the implicit function-to-function-pointer conversion. In any case, this is why void (*p3_foo)() = &foo; works.

The unary *, when applied to a function pointer, yields the pointed-to function, just like it yields the pointed-to object when it is applied to an ordinary pointer to an object.

These rules can be combined. Consider your second to last example, **foo:

  • First, foo is implicitly converted to a pointer to itself and the first * is applied to that function pointer, yielding the function foo again.
  • Then, the result is again implicitly converted to a pointer to itself and the second * is applied, again yielding the function foo.
  • It is then implicitly converted to a function pointer again and assigned to the variable.

You can add as many *s as you like, the result is always the same. The more *s, the merrier.

We can also consider your fifth example, &*foo:

  • First, foo is implicitly converted to a pointer to itself; the unary * is applied, yielding foo again.
  • Then, the & is applied to foo, yielding a pointer to foo, which is assigned to the variable.

The & can only be applied to a function though, not to a function that has been converted to a function pointer (unless, of course, the function pointer is a variable, in which case the result is a pointer-to-a-pointer-to-a-function; for example, you could add to your list void (**pp_foo)() = &p7_foo;).

This is why &&foo doesn't work: &foo is not a function; it is a function pointer that is an rvalue. However, &*&*&*&*&*&*foo would work, as would &******&foo, because in both of those expressions the & is always applied to a function and not to an rvalue function pointer.

Note also that you do not need to use the unary * to make the call via the function pointer; both (*p1_foo)(); and (p1_foo)(); have the same result, again because of the function-to-function-pointer conversion.

Liquidambar answered 1/8, 2011 at 0:38 Comment(16)
So the function name by itself is really a function pointer, right? If so, that clears up the differences between p1, p2, p6, and p7 (i.e. there is none). What about references to function pointers? Is there a difference between p3, p4, and p5?Demerol
@Jimmy: Those aren't references to function pointers, they are just function pointers. &foo takes the address of foo, which results in a function pointer pointing at foo, as one would expect.Lowman
I've updated the answer with a more correct (and far lengthier) explanation. It suffices to say that function pointers in C and C++ are bizarre.Liquidambar
@Dennis: Aha, that would make sense. Is that why you can't have two &'s next to eachother? Is that true in general that you cannot take the address of an address? It seems to me like an address should have an address that is addressable... if that abstraction makes sense.Demerol
@James: Yours is a truly excellent answer. I guess it turns out you make have any number of *s and &s mixed up together (not that you'd want to), just so long as you don't have two &s in a row. But if "the unary &, when applied to a function, yields a pointer to the function, just like it yields the address of an object when it is applied to an object" shouldn't you be able to chain two &s in a row?Demerol
You can't chain & operators for objects either: given int p;, &p yields a pointer to p and is an rvalue expression; the & operator requires an lvalue expression.Liquidambar
(There are a number of good explanations of what lvalues and rvalues are in the answers to "What are rvalues, lvalues, xvalues, glvalues, and prvalues?" That question deals with C++0x features, but the answers do a pretty good job explaining the difference between rvalues and lvalues as well.).Liquidambar
I disagree. The more *'s, the less merry.Meagher
Please do not edit the syntax of my examples. I have picked the examples very specifically to demonstrate features of the language.Liquidambar
When calling the function pointer, the braces are not necessary, though it provides a hint to humans that a function definition for that name isn't likely to be found.Neoteny
There's really no reason to dereference function pointers before calling through them. The only reason I've done so in this answer is to emphasize particular syntactic features and reduce the differences between examples to the minimal number of differences required to demonstrate what I am trying to explain. E.g., in the last sentence, we could just as well say p1_foo() instead of (p1_foo)(), but the "extra" parentheses make it more syntactically similar to (*p1_foo)(), to which we are making a comparison.Liquidambar
As a side note, the C standard explicitly states that a combination of &* cancel out each other (6.5.3.2): "The unary & operator yields the address of its operand." /--/ "If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue.".Bastardize
@JamesMcNellis Excellent answer. I have some questions. Question 1: does all your answer apply to C, or some part is C++ exclusive? Question 2: "For pointers to ordinary functions" - (honest question, not being picky about your writing) is there some kind of non-ordinary function? If yes, what would that be? Question 3: you mention a "function-to-function-pointer" implicit conversion. As I understand, it only goes that way, never the opposite (that is, there is no implicit "function-pointer-to-function" conversion), right? ...Lunette
@JamesMcNellis ... Question 4: in both C and C++, the sizeof(p1_foo) is the size of a pointer (8 bytes, in a 64 bits machine), this is ok. But the sizeof(foo) itself is an error in C++ (invalid application of 'sizeof' to a function type), but is 1 in C. This 1 is the size of what?Lunette
@SethCarnegie Of course James's "The more *s, the merrier." was a joke, and one that made my day to boot!Tetanus
&&foo isn’t even grammatical, since && is the logical-and operator.Hehre
D
16

I think it's also helpful to remember that C is just an abstraction for the underlying machine and this is one of the places where that abstraction is leaking.

From the perspective of the computer, a function is just a memory address which, if executed, performs other instructions. So a function in C is itself modelled as an address, which probably leads to the design that a function is "the same" as the address it points to.

Distemper answered 9/3, 2016 at 4:58 Comment(0)
A
3

If you are still not very convinced with @JamesMcNellis's answer, here is a prove. This is the AST(abstract syntax tree) from Clang compiler. Abstract syntax tree is the internal representation of the program structure inside the compiler.

void func1() {};
void test() {
    func1();
    (*func1)();
    (&func1)();

    void(*func1ptr)(void) = func1;
    func1ptr();
    (*func1ptr)();
    //(&func1ptr)();//error since func1ptr is a variable, &func1ptr is its address which is not callable.
}

AST:

//func1();
|-CallExpr //call the pointer
| `-ImplicitCastExpr //implicitly convert func1 to pointer
|   `-DeclRefExpr //reference func1

//(*func1)();
|-CallExpr //call the pointer
| `-ImplicitCastExpr //implicitly convert the funtion to pointer
|   `-ParenExpr //parentheses
|     `-UnaryOperator //* operator get function from the pointer
|       `-ImplicitCastExpr //implicitly convert func1 to pointer
|         `-DeclRefExpr //reference func1

//(&func1)();
|-CallExpr //call the pointer
| `-ParenExpr //parentheses
|   `-UnaryOperator //& get pointer from func1
|     `-DeclRefExpr //reference func1

//void(*func1ptr)(void) = func1;
|-DeclStmt //define variable func1ptr
| `-VarDecl //define variable func1ptr
|   `-ImplicitCastExpr //implicitly convert func1 to pointer
|     `-DeclRefExpr  //reference func1

//func1ptr();
|-CallExpr  //call the pointer
| `-ImplicitCastExpr //implicitly convert func1ptr to pointer
|   `-DeclRefExpr //reference the variable func1ptr

//(*func1ptr)();
`-CallExpr //call the pointer 
  `-ImplicitCastExpr //implicitly convert the function to pointer
    `-ParenExpr //parentheses
      `-UnaryOperator //* get the function from the pointer
        `-ImplicitCastExpr //implicitly convert func1ptr to pointer
          `-DeclRefExpr //reference the variable func1ptr
Adulthood answered 13/3, 2021 at 12:33 Comment(0)
S
1

& and * are idempotent operations on a symbol declared as a function in C which means func == *func == &func == *&func and therefore *func == **func, but they have different types, so you'll get a warning.

The parameter type of a passed function address to a function can be int () or int (*)(), and it can be passed as *func, func or &func. Calling (&func)() is the same as func() or (*func)(). Godbolt link.

* and & have no meaning on a function symbol, and instead of producing an error, the compiler chooses to interpret it as the address of func in both cases. The function does not exist as a separate pointer, like an array symbol, therefore &arr is the same as arr, because it is not a physical pointer with an address at runtime, it's a logical pointer at compiler level. Furthermore *func would read the first byte of the function code, which is an a code section, and rather than produce a compiler error or allow it to be a runtime error segmentation fault, it's just interpreted by the compiler as the address of the function.

& on a symbol declared as a function pointer however will get the address of the pointer (because it is now an actual pointer variable that manifests on the stack or data section), whereas funcp and *funcp will still be interpreted to be the address of the function.

Schaffer answered 21/3, 2020 at 13:17 Comment(1)
&arr is not at all the same as arr, even though it has the same address.Hehre
B
-1

When calling foo from a pointer, even the parentheses and the asterisk can be omitted, just as directly calling the function with its original name, i.e. (*p1_foo)() is equivalent to p1_foo().

Blight answered 6/10, 2021 at 21:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.