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

17,689

Solution 1

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.

Solution 2

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.

Solution 3

& 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.

Share:
17,689

Related videos on Youtube

Jimmy
Author by

Jimmy

Updated on June 07, 2022

Comments

  • Jimmy
    Jimmy almost 2 years

    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)();
    }
    
  • Jimmy
    Jimmy almost 13 years
    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?
  • Dennis Zickefoose
    Dennis Zickefoose almost 13 years
    @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.
  • James McNellis
    James McNellis almost 13 years
    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.
  • Jimmy
    Jimmy almost 13 years
    @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.
  • Jimmy
    Jimmy almost 13 years
    @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?
  • James McNellis
    James McNellis almost 13 years
    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.
  • James McNellis
    James McNellis almost 13 years
    (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.).
  • Seth Carnegie
    Seth Carnegie almost 13 years
    I disagree. The more *'s, the less merry.
  • James McNellis
    James McNellis about 12 years
    Please do not edit the syntax of my examples. I have picked the examples very specifically to demonstrate features of the language.
  • MauganRa
    MauganRa over 10 years
    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.
  • James McNellis
    James McNellis over 10 years
    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.
  • Lundin
    Lundin over 8 years
    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.".
  • Rafael Eyng
    Rafael Eyng over 4 years
    @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? ...
  • Rafael Eyng
    Rafael Eyng over 4 years
    @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?