Dlaczego definicje wskaźników funkcji działają z dowolną liczbą znaków „&” lub gwiazdek „*”?

216

Dlaczego następujące działania?

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
źródło

Odpowiedzi:

224

Jest w tym kilka elementów, które pozwalają wszystkim kombinacjom operatorów działać w ten sam sposób.

Podstawowym powodem, dla którego wszystkie z tych działań jest to, że funkcja (podobna foo) jest domyślnie konwertowalna na wskaźnik do funkcji. Właśnie dlatego void (*p1_foo)() = foo;działa: foojest domyślnie konwertowany na wskaźnik do siebie i ten wskaźnik jest przypisany p1_foo.

Jednoargumentowy &, po zastosowaniu do funkcji, daje wskaźnik do funkcji, podobnie jak podaje adres obiektu, gdy zostanie zastosowany do obiektu. W przypadku wskaźników do zwykłych funkcji zawsze jest redundantny z powodu niejawnej konwersji funkcji na wskaźnik funkcji. W każdym razie właśnie dlatego void (*p3_foo)() = &foo;działa.

Jednoargumentowy *, po zastosowaniu do wskaźnika funkcji, daje funkcję wskazaną do, podobnie jak daje obiekt wskazany, gdy jest stosowany do zwykłego wskaźnika do obiektu.

Reguły te można łączyć. Rozważ swój przykład od drugiego do ostatniego **foo:

  • Po pierwsze, foojest domyślnie konwertowany na wskaźnik do siebie, a pierwszy *jest stosowany do tego wskaźnika funkcji, dając fooponownie funkcję .
  • Następnie wynik jest ponownie domyślnie konwertowany na wskaźnik do siebie, a drugi *jest stosowany, ponownie dając funkcję foo.
  • Następnie jest ponownie domyślnie konwertowany na wskaźnik funkcji i przypisywany do zmiennej.

Możesz dodać dowolną liczbę *s, wynik jest zawsze taki sam. Im więcej *s, tym lepiej.

Możemy również rozważyć Twój piąty przykład &*foo:

  • Po pierwsze, foojest domyślnie konwertowany na wskaźnik do siebie; jednoargumentowy *jest stosowany, ustępując fooponownie.
  • Następnie &stosuje się do foo, uzyskując wskaźnik do foo, który jest przypisany do zmiennej.

&Mogą być stosowane tylko do funkcji chociaż nie do funkcji, które zostały zamienione na wskaźnik funkcyjnych (o ile, oczywiście, wskaźnik funkcji jest zmienna, przy czym wynik jest wskaźnik do a-pointer- do funkcji; na przykład możesz dodać do swojej listy void (**pp_foo)() = &p7_foo;).

Oto dlaczego &&foonie działa: &foonie jest funkcją; jest wskaźnikiem funkcji, który jest wartością. Jednak &*&*&*&*&*&*foodziałałoby to tak , jakby działało, &******&fooponieważ w obu tych wyrażeniach &zawsze jest stosowane do funkcji, a nie do wskaźnika funkcji wartości.

Zauważ też, że nie trzeba używać unarnego, *aby wykonać połączenie za pomocą wskaźnika funkcji; oba (*p1_foo)();i (p1_foo)();mają ten sam wynik, ponownie z powodu konwersji funkcji na wskaźnik funkcji.

James McNellis
źródło
2
@ Jimmy: To nie są odniesienia do wskaźników funkcji, to tylko wskaźniki funkcji. &fooprzyjmuje adres foo, co powoduje, że wskaźnik funkcji wskazuje na foo, jak można by się spodziewać.
Dennis Zickefoose
2
Nie można również łączyć &operatorów dla obiektów: dane int p;, &pzwraca wskaźnik do pi jest wyrażeniem wartości; &operatora wymaga lwartością ekspresji.
James McNellis
12
Nie zgadzam się. Im więcej *, tym mniej wesoło .
Seth Carnegie
28
Proszę nie edytować składni moich przykładów. Wybrałem przykłady bardzo konkretnie, aby zademonstrować cechy języka.
James McNellis,
7
Na marginesie, norma C wyraźnie stwierdza, że ​​kombinacja &*wzajemnego znoszenia się (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.".
Lundin,
9

Myślę, że warto również pamiętać, że C jest tylko abstrakcją dla podstawowej maszyny i jest to jedno z miejsc, w których ta abstrakcja przecieka.

Z perspektywy komputera funkcja jest tylko adresem pamięci, który, jeśli zostanie wykonany, wykonuje inne instrukcje. Tak więc funkcja w C jest sama modelowana jako adres, co prawdopodobnie prowadzi do projektu, że funkcja jest „taka sama” jak adres, na który wskazuje.

madumlao
źródło
0

&i *są idempotencjalnymi operacjami na symbolu zadeklarowanym jako funkcja w C, co oznacza func == *func == &func == *&funci dlatego*func == **func

Oznacza to, że typ int ()jest taki sam jak int (*)()parametr funkcji, a zdefiniowaną funkcję można przekazać jako *func, funclub &func. (&func)()jest taki sam jak func(). Link Godbolt.

Dlatego funkcja jest naprawdę adresem *i &nie ma żadnego znaczenia, a zamiast generowania błędu kompilator decyduje się interpretować go jako adres func.

&na symbolu zadeklarowanym jako wskaźnik funkcji uzyska jednak adres wskaźnika (ponieważ ma on teraz odrębny cel), funcpa jednocześnie *funcpbędzie identyczny

Lewis Kelsey
źródło