Warum funktionieren Funktionszeigerdefinitionen mit einer beliebigen Anzahl von kaufmännischen Und-Zeichen '&' oder Sternchen '*'?

216

Warum arbeiten die folgenden?

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
quelle

Antworten:

224

Es gibt einige Teile, mit denen alle diese Operatorkombinationen auf die gleiche Weise funktionieren können.

Der grundlegende Grund, warum all diese Arbeiten funktionieren, ist, dass eine Funktion (wie foo) implizit in einen Zeiger auf die Funktion konvertierbar ist. Aus diesem Grund void (*p1_foo)() = foo;funktioniert works: foowird implizit in einen Zeiger auf sich selbst konvertiert und dieser Zeiger wird zugewiesen p1_foo.

Das Unäre &liefert, wenn es auf eine Funktion angewendet wird, einen Zeiger auf die Funktion, genau wie es die Adresse eines Objekts liefert, wenn es auf ein Objekt angewendet wird. Bei Zeigern auf gewöhnliche Funktionen ist sie aufgrund der impliziten Umwandlung von Funktion in Funktionszeiger immer redundant. In jedem Fall void (*p3_foo)() = &foo;funktioniert dies deshalb .

Das Unäre *ergibt, wenn es auf einen Funktionszeiger angewendet wird, die Funktion, auf die gezeigt wird, genauso wie es das Objekt zeigt, auf das gezeigt wird, wenn es auf einen gewöhnlichen Zeiger auf ein Objekt angewendet wird.

Diese Regeln können kombiniert werden. Betrachten Sie Ihr vorletztes Beispiel **foo:

  • Erstens foowird implizit in einen Zeiger auf sich selbst konvertiert und der erste *wird auf diesen Funktionszeiger angewendet, wodurch die Funktion fooerneut erhalten wird.
  • Dann wird das Ergebnis wieder implizit in einen Zeiger auf sich selbst konvertiert und das zweite *wird angewendet, was wiederum die Funktion ergibt foo.
  • Es wird dann implizit wieder in einen Funktionszeiger konvertiert und der Variablen zugewiesen.

Sie können beliebig viele *s hinzufügen , das Ergebnis ist immer das gleiche. Je mehr *s, desto besser.

Wir können auch Ihr fünftes Beispiel betrachten &*foo:

  • Erstens foowird implizit in einen Zeiger auf sich selbst konvertiert; Das Unäre *wird angewendet und gibt foowieder nach.
  • Dann wird das auf &angewendet foo, was einen Zeiger auf fooergibt, der der Variablen zugewiesen ist.

Das &kann jedoch nur auf eine Funktion angewendet werden, nicht auf eine Funktion, die in einen Funktionszeiger konvertiert wurde (es sei denn, der Funktionszeiger ist natürlich eine Variable. In diesem Fall ist das Ergebnis ein Zeiger auf einen Zeiger. zu einer Funktion (zum Beispiel könnten Sie Ihrer Liste hinzufügen void (**pp_foo)() = &p7_foo;).

Deshalb &&foofunktioniert es nicht: &fooist keine Funktion; Es ist ein Funktionszeiger, der ein Wert ist. Dies &*&*&*&*&*&*foowürde jedoch genauso funktionieren, &******&fooda in beiden Ausdrücken das &immer auf eine Funktion und nicht auf einen r-Wert-Funktionszeiger angewendet wird.

Beachten Sie auch, dass Sie das Unary nicht verwenden müssen, *um den Aufruf über den Funktionszeiger zu tätigen. beide (*p1_foo)();und (p1_foo)();haben das gleiche Ergebnis, wiederum aufgrund der Umwandlung von Funktion in Funktionszeiger.

James McNellis
quelle
2
@ Jimmy: Das sind keine Verweise auf Funktionszeiger, sondern nur Funktionszeiger. &foonimmt die Adresse von foo, was dazu führt, dass ein Funktionszeiger foowie erwartet auf zeigt.
Dennis Zickefoose
2
Sie können auch keine &Operatoren für Objekte verketten : angegeben int p;, &pliefert einen Zeiger auf pund ist ein rWert-Ausdruck; Der &Operator benötigt einen lvalue-Ausdruck.
James McNellis
12
Ich bin nicht einverstanden. Je mehr *, desto weniger fröhlich .
Seth Carnegie
28
Bitte bearbeiten Sie nicht die Syntax meiner Beispiele. Ich habe die Beispiele sehr speziell ausgewählt, um Merkmale der Sprache zu demonstrieren.
James McNellis
7
Als Randnotiz besagt der C-Standard ausdrücklich, dass eine Kombination von &*sich gegenseitig aufhebt (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

Ich denke, es ist auch hilfreich, sich daran zu erinnern, dass C nur eine Abstraktion für die zugrunde liegende Maschine ist und dies einer der Orte ist, an denen diese Abstraktion leckt.

Aus der Sicht des Computers ist eine Funktion nur eine Speicheradresse, die, wenn sie ausgeführt wird, andere Anweisungen ausführt. Eine Funktion in C wird also selbst als Adresse modelliert, was wahrscheinlich dazu führt, dass eine Funktion "dieselbe" ist wie die Adresse, auf die sie zeigt.

Madumlao
quelle
0

&und *sind idempotente Operationen an einem Symbol, das in C als Funktion deklariert ist, was bedeutet func == *func == &func == *&funcund daher*func == **func

Es bedeutet , dass der Typ int ()der gleiche wie int (*)()als Funktionsparameter und eine definierte func kann als übergeben werden *func, funcoder &func. (&func)()ist das gleiche wie func(). Godbolt Link.

Eine Funktion ist also wirklich eine Adresse *und &hat keine Bedeutung. Anstatt einen Fehler zu erzeugen, interpretiert der Compiler sie als Adresse von func.

&Bei einem als Funktionszeiger deklarierten Symbol wird jedoch die Adresse des Zeigers abgerufen (da er jetzt einen separaten Zweck hat), während funcpund *funcpidentisch ist

Lewis Kelsey
quelle