Warum funktionieren Funktionszeiger-Definitionen mit beliebig vielen kaufmännischen Und-Zeichen ‘&’ oder Sternchen ‘*’?

Lesezeit: 6 Minuten

Warum funktionieren Funktionszeiger Definitionen mit beliebig vielen kaufmannischen Und Zeichen oder
Jimmy

Warum funktioniert Folgendes?

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)();
}

Warum funktionieren Funktionszeiger Definitionen mit beliebig vielen kaufmannischen Und Zeichen oder
James McNellis

Es gibt ein paar Teile, die es allen diesen Kombinationen von Operatoren ermöglichen, auf die gleiche Weise zu arbeiten.

Der grundlegende Grund, warum all dies funktioniert, ist, dass eine Funktion (wie foo) ist implizit in einen Zeiger auf die Funktion konvertierbar. Deshalb void (*p1_foo)() = foo; funktioniert: foo wird implizit in einen Zeiger auf sich selbst umgewandelt und diesem Zeiger zugewiesen p1_foo.

Das Unäre &, wenn es auf eine Funktion angewendet wird, ergibt einen Zeiger auf die Funktion, genauso wie es die Adresse eines Objekts liefert, wenn es auf ein Objekt angewendet wird. Für Zeiger auf gewöhnliche Funktionen ist es wegen der impliziten Konvertierung von Funktion zu Funktionszeiger immer redundant. Jedenfalls deshalb void (*p3_foo)() = &foo; funktioniert.

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

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

  • Zuerst, foo wird implizit in einen Zeiger auf sich selbst und den ersten umgewandelt * wird auf diesen Funktionszeiger angewendet und ergibt die Funktion foo aufs Neue.
  • Dann wird das Ergebnis wieder implizit in einen Zeiger auf sich selbst und den zweiten konvertiert * angewendet, was wiederum die Funktion ergibt foo.
  • Anschließend wird er implizit wieder in einen Funktionszeiger umgewandelt und der Variablen zugewiesen.

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

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

  • Zuerst, foo wird implizit in einen Zeiger auf sich selbst konvertiert; das unäre * aufgetragen wird, nachgebend foo aufs Neue.
  • Dann ist die & wird angewendet auf foowas einen Zeiger auf ergibt foodie der Variablen zugewiesen wird.

Die & kann jedoch nur auf eine Funktion angewendet werden, nicht auf eine Funktion, die in einen Funktionszeiger umgewandelt wurde (außer natürlich, der Funktionszeiger ist eine Variable, in welchem ​​Fall das Ergebnis ein Zeiger-auf-einen-Zeiger-auf ist -a-Funktion; Sie könnten beispielsweise zu Ihrer Liste hinzufügen void (**pp_foo)() = &p7_foo;).

Deshalb &&foo geht nicht: &foo ist keine Funktion; es ist ein Funktionszeiger, der ein Rvalue ist. Aber, &*&*&*&*&*&*foo würde funktionieren, wie würde &******&fooweil in diesen beiden Ausdrücken die & wird immer auf eine Funktion und nicht auf einen Rvalue-Funktionszeiger angewendet.

Beachten Sie auch, dass Sie das unäre nicht verwenden müssen * um den Aufruf über den Funktionszeiger zu tätigen; beide (*p1_foo)(); und (p1_foo)(); haben das gleiche Ergebnis, wiederum wegen der Funktion-zu-Funktion-Zeiger-Konvertierung.

  • @Jimmy: Das sind keine Verweise auf Funktionszeiger, sondern nur Funktionszeiger. &foo nimmt die Adresse von foowas dazu führt, dass ein Funktionszeiger auf zeigt foowie man erwarten würde.

    – Dennis Zickefoose

    1. August 2011 um 0:59 Uhr

  • Du kannst nicht ketten & Operatoren für Objekte entweder: gegeben int p;, &p liefert einen Zeiger auf p und ist ein Rvalue-Ausdruck; der & Der Operator erfordert einen lvalue-Ausdruck.

    – James McNellis

    1. August 2011 um 1:15 Uhr

  • Ich bin nicht einverstanden. Je mehr *‘s, die weniger fröhlich.

    – Seth Carnegie

    1. August 2011 um 1:28 Uhr


  • Bitte bearbeiten Sie nicht die Syntax meiner Beispiele. Ich habe die Beispiele sehr gezielt ausgewählt, um Besonderheiten der Sprache zu demonstrieren.

    – James McNellis

    4. März 2012 um 5:58 Uhr

  • Als Nebenbemerkung besagt der C-Standard ausdrücklich, dass eine Kombination von &* gegenseitig aufheben (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.".

    – Ludin

    30. November 2015 um 15:21 Uhr


Ich denke, es ist auch hilfreich, sich daran zu erinnern, dass C nur eine Abstraktion für die zugrunde liegende Maschine ist und dies eine der Stellen 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 zu dem Design führt, dass eine Funktion “die gleiche” ist wie die Adresse, auf die sie zeigt.

1647207009 481 Warum funktionieren Funktionszeiger Definitionen mit beliebig vielen kaufmannischen Und Zeichen oder
Lewis Kelsey

& und * sind idempotente Operationen auf einem Symbol, das in C als Funktion deklariert ist, was bedeutet func == *func == &func == *&func und deshalb *func == **funcaber sie haben unterschiedliche Typen, daher erhalten Sie eine Warnung.

Der Parametertyp einer übergebenen Funktionsadresse an eine Funktion kann sein int () oder int (*)()und es kann als übergeben werden *func, func oder &func. Berufung (&func)() ist das gleiche wie func() oder (*func)(). Godbolt-Link.

* und & haben für ein Funktionssymbol keine Bedeutung, und anstatt einen Fehler zu erzeugen, interpretiert der Compiler es in beiden Fällen als Adresse von func. Die Funktion existiert also nicht als separater Zeiger, wie ein Array-Symbol &arr ist das gleiche wie arr, da es sich nicht um einen physischen Zeiger mit einer Adresse zur Laufzeit handelt, sondern um einen logischen Zeiger auf Compiler-Ebene. Außerdem *func würde das erste Byte des Funktionscodes lesen, bei dem es sich um einen Codeabschnitt handelt, und anstatt einen Compilerfehler zu erzeugen oder zuzulassen, dass es sich um einen Laufzeitfehler-Segmentierungsfehler handelt, wird es vom Compiler nur als Adresse der Funktion interpretiert.

& auf einem als Funktionszeiger deklarierten Symbol erhält jedoch die Adresse des Zeigers (da es sich jetzt um eine tatsächliche Zeigervariable handelt, die sich auf dem Stapel oder im Datenabschnitt manifestiert), wohingegen funcp und *funcp wird weiterhin als Adresse der Funktion interpretiert.

Beim Anrufen foo von einem Zeiger können sogar die Klammern und das Sternchen weggelassen werden, genauso wie der direkte Aufruf der Funktion mit ihrem ursprünglichen Namen, dh (*p1_foo)() ist äquivalent zu pi_foo().

Wenn Sie von der Antwort von @JamesMcNellis immer noch nicht sehr überzeugt sind, finden Sie hier einen Beweis. Dies ist der AST (abstrakter Syntaxbaum) vom Clang-Compiler. Der abstrakte Syntaxbaum ist die interne Darstellung der Programmstruktur innerhalb des Compilers.

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

999070cookie-checkWarum funktionieren Funktionszeiger-Definitionen mit beliebig vielen kaufmännischen Und-Zeichen ‘&’ oder Sternchen ‘*’?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy