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

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 foo
was einen Zeiger auf ergibt foo
die 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 &******&foo
weil 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.
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.

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 == **func
aber 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
9990700cookie-checkWarum funktionieren Funktionszeiger-Definitionen mit beliebig vielen kaufmännischen Und-Zeichen ‘&’ oder Sternchen ‘*’?yes