Wie erfolgt die Dereferenzierung eines Funktionszeigers?

Lesezeit: 8 Minuten

Wie erfolgt die Dereferenzierung eines Funktionszeigers
Laser

Warum und wie bewirkt die Dereferenzierung eines Funktionszeigers einfach “nichts”?

Das ist, wovon ich spreche:

#include<stdio.h>

void hello() { printf("hello"); }

int main(void) { 
    (*****hello)(); 
}

Aus einem Kommentar hier:

Funktionszeiger dereferenzieren problemlos, aber der resultierende Funktionsbezeichner wird sofort wieder in einen Funktionszeiger umgewandelt


Und aus einer Antwort hier:

Das Dereferenzieren (wie Sie denken) eines Funktionszeigers bedeutet: Zugriff auf einen CODE-Speicher, als wäre er ein DATA-Speicher.

Der Funktionszeiger soll nicht auf diese Weise dereferenziert werden. Stattdessen heißt es.

Ich würde einen Namen “dereference” neben “call” verwenden. Es ist in Ordnung.

Wie auch immer: C ist so konzipiert, dass sowohl der Bezeichner des Funktionsnamens als auch der Zeiger der Variablen, die die Funktion enthalten, dasselbe bedeuten: Adresse zum CODE-Speicher. Und es erlaubt, zu diesem Speicher zu springen, indem die call () -Syntax entweder für einen Bezeichner oder eine Variable verwendet wird.


Wie exakt funktioniert die Dereferenzierung eines Funktionszeigers?

Wie erfolgt die Dereferenzierung eines Funktionszeigers
Norman Ramsey

Es ist nicht ganz die richtige Frage. Zumindest für C lautet die richtige Frage

Was passiert mit einem Funktionswert in einem Rvalue-Kontext?

(Ein Rvalue-Kontext ist überall dort, wo ein Name oder eine andere Referenz erscheint, wo er als Wert verwendet werden sollte, und nicht als Ort – im Grunde überall außer auf der linken Seite einer Zuweisung. Der Name selbst kommt von der rechts-Handseite einer Aufgabe.)

OK, was passiert also mit einem Funktionswert in einem Rvalue-Kontext? Es wird sofort und implizit in einen Zeiger auf den ursprünglichen Funktionswert umgewandelt. Wenn Sie diesen Zeiger mit dereferenzieren *erhalten Sie denselben Funktionswert wieder zurück, der sofort und implizit in einen Zeiger umgewandelt wird. Und Sie können dies so oft tun, wie Sie möchten.

Zwei ähnliche Experimente, die Sie ausprobieren können:

  • Was passiert, wenn Sie einen Funktionszeiger in einer dereferenzieren? Wert Kontext – die linke Seite einer Zuweisung. (Die Antwort wird ungefähr das sein, was Sie erwarten, wenn Sie bedenken, dass Funktionen unveränderlich sind.)

  • Ein Array-Wert wird in einem lvalue-Kontext ebenfalls in einen Zeiger konvertiert, aber er wird in einen Zeiger auf die konvertiert Element Typ, nicht auf einen Zeiger auf das Array. Wenn Sie es dereferenzieren, erhalten Sie daher ein Element und kein Array, und der von Ihnen gezeigte Wahnsinn tritt nicht auf.

Hoffe das hilft.

PS Bezüglich warum Da ein Funktionswert implizit in einen Zeiger umgewandelt wird, lautet die Antwort, dass es für diejenigen von uns, die Funktionszeiger verwenden, eine große Bequemlichkeit ist, sie nicht verwenden zu müssen &ist überall. Es gibt auch einen doppelten Komfort: Ein Funktionszeiger in Aufrufposition wird automatisch in einen Funktionswert konvertiert, sodass Sie nichts schreiben müssen * über einen Funktionszeiger aufrufen.

PPS Im Gegensatz zu C-Funktionen können C++-Funktionen überladen werden, und ich bin nicht qualifiziert, die Funktionsweise der Semantik in C++ zu kommentieren.

  • Könnten Sie bitte näher auf “… implizit in einen Zeiger auf den ursprünglichen Funktionswert konvertiert” eingehen? Meinst du den Rückgabewert der Funktion? Wenn ja, bedeutet das, dass der Compiler diesen Rückgabewert automatisch als lvalue behält, obwohl der Rückgabewert der Funktion ein rvalue war. Danke!

    – Lehrermikro

    31. März 2016 um 17:19 Uhr


  • Du schreibst ein Funktionszeiger in Aufrufposition wird automatisch in einen Funktionswert umgewandelt – aber die Wahrheit ist das Gegenteil. Sowohl Funktionsaufruf- als auch Array-Indizierungsoperatoren erfordern, dass der Operand „Funktion“/„Array“ tatsächlich so sein muss ein Zeiger.

    – Antti Haapala – Слава Україні

    22. Juni 2018 um 20:02 Uhr

Wie erfolgt die Dereferenzierung eines Funktionszeigers
Kartoffelklatsche

C++03 §4.3/1:

Ein L-Wert vom Funktionstyp T kann in einen R-Wert vom Typ „Zeiger auf T“ konvertiert werden. Das Ergebnis ist ein Zeiger auf die Funktion.

Wenn Sie versuchen, eine ungültige Operation für eine Funktionsreferenz auszuführen, z. B. die unäre * Operator versucht die Sprache als Erstes eine Standardkonvertierung. Es ist wie das Konvertieren von an int beim Hinzufügen zu a float. Verwenden * auf eine Funktionsreferenz bewirkt, dass die Sprache stattdessen ihren Zeiger nimmt, der in Ihrem Beispiel Quadrat 1 ist.

Ein weiterer Fall, in dem dies zutrifft, ist die Zuweisung eines Funktionszeigers.

void f() {
    void (*recurse)() = f; // "f" is a reference; implicitly convert to ptr.
    recurse(); // call operator is defined for pointers
}

Beachten Sie, dass dies nicht anders herum arbeiten.

void f() {
    void (&recurse)() = &f; // "&f" is a pointer; ERROR can't convert to ref.
    recurse(); // OK - call operator is *separately* defined for references
}

Funktionsreferenzvariablen sind nett, weil sie (theoretisch habe ich das nie getestet) dem Compiler mitteilen, dass eine indirekte Verzweigung möglicherweise unnötig ist, wenn sie in einem einschließenden Gültigkeitsbereich initialisiert wird.

In C99 ergibt die Dereferenzierung eines Funktionszeigers einen Funktionsbezeichner. §6.3.2.1/4:

Ein Funktionsbezeichner ist ein Ausdruck, der einen Funktionstyp hat. Außer wenn es sich um den Operanden des sizeof-Operators oder des unären &-Operators handelt, wird ein Funktionsbezeichner vom Typ „Funktionsrückgabetyp“ in einen Ausdruck vom Typ „Zeiger auf Funktionsrückgabetyp“ konvertiert.

Dies ähnelt eher der Antwort von Norman, aber insbesondere C99 hat kein Konzept von rvalues.

  • auf eine Funktionsreferenz” Tatsächlich, ein Ausdruck kann keinen Referenztyp haben. Ein Ausdruck kann Rvalue oder Lvalue sein.

    – Neugieriger

    3. Oktober 2011 um 19:46 Uhr


1647088815 904 Wie erfolgt die Dereferenzierung eines Funktionszeigers
explogx

Es passiert mit ein paar impliziten Konvertierungen. In der Tat, gemäß dem C-Standard:

ISO/IEC 2011, Abschnitt 6.3.2.1 Lvalues, Arrays, and Function Designators, Absatz 4

EIN Funktionsbezeichner ist ein Ausdruck vom Funktionstyp. Außer wenn es der Operand von ist sizeof Operator oder das Unäre & Operator, ein Funktionsbezeichner vom Typ „Funktion, die zurückkehrt Art“ wird in einen Ausdruck konvertiert, der den Typ „Zeiger auf zurückkehrende Funktion hat Art“.

Betrachten Sie den folgenden Code:

void func(void);

int main(void)
{
    void (*ptr)(void) = func;
    return 0;
}

Hier der Funktionsbezeichner func hat den Typ „Funktion kehrt zurück void“, wird aber sofort in einen Ausdruck konvertiert, der den Typ „Zeiger auf zurückkehrende Funktion hat void“. Allerdings, wenn Sie schreiben

void (*ptr)(void) = &func;

dann der Funktionsbezeichner func hat den Typ „Funktion kehrt zurück void” aber das unäre & Der Operator nimmt explizit die Adresse dieser Funktion und ergibt schließlich den Typ „Zeiger auf die Funktion, die zurückkehrt void“.

Dies wird in der C-Norm erwähnt:

ISO/IEC 2011, Abschnitt 6.5.3.2 Adress- und Indirektionsoperatoren, Absatz 3

Das Unäre & Operator liefert die Adresse seines Operanden. Wenn der Operand vom Typ „Art“, hat das Ergebnis den Typ „Zeiger auf Art“.

Insbesondere ist das Dereferenzieren eines Funktionszeigers überflüssig. Nach dem C-Standard:

ISO/IEC 2011, Abschnitt 6.5.2.2 Funktionsaufrufe, Absatz 1

Der Ausdruck, der die aufgerufene Funktion bezeichnet, muss vom Typ „Zeiger auf zurückkehrende Funktion“ sein void“ oder einen vollständigen Objekttyp zurückgeben, der kein Array-Typ ist. Meistens ist dies das Ergebnis der Konvertierung eines Bezeichners, der ein Funktionsbezeichner ist.

ISO/IEC 2011, Abschnitt 6.5.3.2 Adress- und Indirektionsoperatoren, Absatz 4

Das Unäre * Operator bezeichnet Indirektion. Wenn der Operand auf eine Funktion zeigt, ist das Ergebnis ein Funktionsbezeichner.

Also beim Schreiben

ptr();

Der Funktionsaufruf wird ohne implizite Konvertierung ausgewertet, weil ptr ist bereits ein Zeiger auf Funktion. Wenn Sie es explizit mit dereferenzieren

(*ptr)();

dann ergibt die Dereferenzierung den Typ „Funktion kehrt zurück void“, der sofort wieder in den Typ „Zeiger auf zurückkehrende Funktion“ umgewandelt wird void“ und der Funktionsaufruf erfolgt. Beim Schreiben eines Ausdrucks bestehend aus x einstellig * indirekte Operatoren wie z

(****ptr)();

dann wiederholen Sie einfach die impliziten Konvertierungen x mal.


Es macht Sinn, dass der Aufruf von Funktionen Funktionszeiger beinhaltet. Vor dem Ausführen einer Funktion schiebt ein Programm alle Parameter für die Funktion in umgekehrter Reihenfolge, in der sie dokumentiert sind, auf den Stapel. Dann gibt das Programm a aus call Anweisung, die angibt, welche Funktion gestartet werden soll. Die call Anleitung macht zwei Dinge:

  1. Zuerst schiebt es die Adresse des nächsten Befehls, die die Rücksprungadresse ist, auf den Stapel.
  2. Dann modifiziert er den Befehlszeiger %eip um auf den Beginn der Funktion zu zeigen.

Da das Aufrufen einer Funktion das Modifizieren eines Befehlszeigers beinhaltet, der eine Speicheradresse ist, ist es sinnvoll, dass der Compiler einen Funktionsbezeichner implizit in einen Zeiger auf eine Funktion umwandelt.


Auch wenn es unsinnig erscheinen mag, diese impliziten Konvertierungen zu haben, kann es in C (im Gegensatz zu C++, das Namensräume hat) nützlich sein, den durch einen Strukturbezeichner definierten Namensraum zu nutzen, um Variablen zu kapseln.

Betrachten Sie den folgenden Code:

void create_person(void);
void update_person(void);
void delete_person(void);

struct Person {
    void (*create)(void);
    void (*update)(void);
    void (*delete)(void);
};

static struct Person person = {
    .create = &create_person,
    .update = &update_person,
    .delete = &delete_person,
};

int main(void)
{
    person.create();
    person.update();
    person.delete();
    return 0;
}

Es ist möglich, die Implementierung der Bibliothek in anderen Übersetzungseinheiten zu verbergen und zu wählen, nur die Struktur, die die Zeiger auf Funktionen kapselt, offenzulegen, um sie anstelle von zu verwenden tatsächlich Funktionsbezeichner.

  • Sehr gute Erklärung.

    – balu

    3. März um 3:49

Versetzen Sie sich in die Lage des Compiler-Schreibers. Ein Funktionszeiger hat eine wohldefinierte Bedeutung, er ist ein Zeiger auf einen Byte-Blob, der Maschinencode darstellt.

Was tun Sie, wenn der Programmierer einen Funktionszeiger dereferenziert? Nehmen Sie die ersten (oder 8) Bytes des Maschinencodes und interpretieren das als Zeiger neu? Die Chancen stehen etwa 2 Milliarden zu eins, dass dies nicht funktionieren wird. Deklarieren Sie UB? Davon geht schon einiges rum. Oder ignorierst du den Versuch einfach? Du kennst die Antwort.

Wie genau funktioniert die Dereferenzierung eines Funktionszeigers?

Zwei schritte. Der erste Schritt erfolgt zur Kompilierzeit, der zweite zur Laufzeit.

In Schritt eins sieht der Compiler, dass er einen Zeiger und einen Kontext hat, in dem dieser Zeiger dereferenziert wird (z (*pFoo)() ), sodass Code für diese Situation generiert wird, Code, der in Schritt 2 verwendet wird.

In Schritt 2 wird zur Laufzeit der Code ausgeführt. Der Zeiger enthält einige Bytes, die angeben, welche Funktion als nächstes ausgeführt werden soll. Diese Bytes werden irgendwie in die CPU geladen. Ein häufiger Fall ist eine CPU mit einem expliziten CALL [register] Anweisung. Auf solchen Systemen kann ein Funktionszeiger einfach die Adresse einer Funktion im Speicher sein, und der Dereferenzierungscode tut nichts anderes, als diese Adresse in ein Register zu laden, gefolgt von a CALL [register] Anweisung.

993630cookie-checkWie erfolgt die Dereferenzierung eines Funktionszeigers?

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

Privacy policy