Ich war immer ein bisschen ratlos, wenn ich den Code anderer Leute las, der typedefs für Zeiger auf Funktionen mit Argumenten hatte. Ich erinnere mich, dass ich eine Weile gebraucht habe, um zu einer solchen Definition zu gelangen, während ich versucht habe, einen numerischen Algorithmus zu verstehen, der vor einiger Zeit in C geschrieben wurde. Könnten Sie also Ihre Tipps und Gedanken darüber teilen, wie Sie gute Typedefs für Zeiger auf Funktionen (Do's und Do not's) schreiben, warum sie nützlich sind und wie Sie die Arbeit anderer verstehen? Vielen Dank!
c
function-pointers
typedef
nbro
quelle
quelle
Antworten:
Betrachten Sie die
signal()
Funktion aus dem C-Standard:Völlig dunkel offensichtlich - es ist eine Funktion, die zwei Argumente akzeptiert, eine Ganzzahl und einen Zeiger auf eine Funktion, die eine Ganzzahl als Argument verwendet und nichts zurückgibt, und sie (
signal()
) gibt einen Zeiger auf eine Funktion zurück, die eine Ganzzahl als Argument verwendet und zurückgibt nichts.Wenn Sie schreiben:
dann können Sie stattdessen deklarieren
signal()
als:Dies bedeutet dasselbe, wird jedoch normalerweise als etwas leichter lesbar angesehen. Es ist klarer, dass die Funktion a
int
und a nimmt und aSignalHandler
zurückgibtSignalHandler
.Es ist allerdings etwas gewöhnungsbedürftig. Das einzige, was Sie jedoch nicht tun können, ist das Schreiben einer Signalhandlerfunktion unter Verwendung der
SignalHandler
typedef
in der Funktionsdefinition.Ich gehöre immer noch zur alten Schule, die es vorzieht, einen Funktionszeiger wie folgt aufzurufen:
Moderne Syntax verwendet nur:
Ich kann sehen, warum das funktioniert - ich möchte nur lieber wissen, dass ich suchen muss, wo die Variable initialisiert wird, anstatt nach einer aufgerufenen Funktion
functionpointer
.Sam kommentierte:
Lass es uns erneut versuchen. Die erste davon wird direkt aus dem C-Standard entfernt - ich habe sie erneut getippt und überprüft, ob die Klammern richtig waren (erst, nachdem ich sie korrigiert habe - es ist ein schwer zu merkender Cookie).
Denken Sie zunächst daran, dass
typedef
ein Alias für einen Typ eingeführt wird. Der Alias ist alsoSignalHandler
und sein Typ ist:Der Teil "gibt nichts zurück" wird geschrieben
void
. Das Argument, das eine ganze Zahl ist, ist (ich vertraue) selbsterklärend. Die folgende Notation ist einfach (oder nicht), wie C einen Zeiger auf eine Funktion buchstabiert, wobei die angegebenen Argumente verwendet und der angegebene Typ zurückgegeben werden:Nachdem ich den Signalhandlertyp erstellt habe, kann ich damit Variablen deklarieren und so weiter. Beispielsweise:
Bitte beachten Sie, wie Sie die Verwendung
printf()
in einem Signalhandler vermeiden können .Was haben wir hier also getan - abgesehen davon, dass 4 Standard-Header weggelassen wurden, die erforderlich wären, damit der Code sauber kompiliert werden kann?
Die ersten beiden Funktionen sind Funktionen, die eine einzelne Ganzzahl annehmen und nichts zurückgeben. Einer von ihnen kehrt dank dem überhaupt nicht zurück
exit(1);
, der andere kehrt jedoch nach dem Drucken einer Nachricht zurück. Beachten Sie, dass der C-Standard es Ihnen nicht erlaubt, sehr viel in einem Signalhandler zu tun. POSIX ist etwas großzügiger in Bezug auf das, was erlaubt ist, sanktioniert jedoch offiziell keine Anrufefprintf()
. Ich drucke auch die empfangene Signalnummer aus. In deralarm_handler()
Funktion ist der Wert immerSIGALRM
so, dass dies das einzige Signal ist, für das es sich um einen Handler handelt, das jedochsignal_handler()
möglicherweiseSIGINT
oder erhältSIGQUIT
als Signalnummer, da für beide dieselbe Funktion verwendet wird.Dann erstelle ich ein Array von Strukturen, in denen jedes Element eine Signalnummer und den für dieses Signal zu installierenden Handler identifiziert. Ich habe mich für 3 Signale entschieden. Ich würde oft Sorgen über
SIGHUP
,SIGPIPE
undSIGTERM
auch , und darüber , ob sie (definiert sind#ifdef
bedingte Kompilierung), aber das erschwert nur die Dinge. Ich würde wahrscheinlich auch POSIXsigaction()
anstelle von verwendensignal()
, aber das ist ein anderes Problem. Bleiben wir bei dem, womit wir angefangen haben.Die
main()
Funktion durchläuft die Liste der zu installierenden Handler. Für jeden Handler wird zuerst aufgerufensignal()
, um herauszufinden, ob der Prozess das Signal derzeit ignoriert, und dabeiSIG_IGN
als Handler installiert , wodurch sichergestellt wird, dass das Signal ignoriert bleibt. Wenn das Signal zuvor nicht ignoriert wurde, wird essignal()
erneut aufgerufen , diesmal um den bevorzugten Signalhandler zu installieren. (Der andere Wert ist vermutlichSIG_DFL
der Standard-Signalhandler für das Signal.) Da der erste Aufruf von 'signal ()' den Handler auf den vorherigen Fehlerhandler setztSIG_IGN
und diesensignal()
zurückgibt, muss der Wertold
nach derif
Anweisung seinSIG_IGN
- daher die Behauptung. (Nun, es könnte seinSIG_ERR
wenn etwas dramatisch schief gelaufen ist - aber dann würde ich das aus dem Assert-Firing erfahren.)Das Programm erledigt dann seine Aufgaben und wird normal beendet.
Beachten Sie, dass der Name einer Funktion als Zeiger auf eine Funktion des entsprechenden Typs angesehen werden kann. Wenn Sie die Klammern für Funktionsaufrufe nicht anwenden - wie zum Beispiel bei den Initialisierern - wird der Funktionsname zu einem Funktionszeiger. Aus diesem Grund ist es auch sinnvoll, Funktionen über die
pointertofunction(arg1, arg2)
Notation aufzurufen . Wenn Sie sehenalarm_handler(1)
, können Sie davon ausgehen, dass diesalarm_handler
ein Zeiger auf die Funktion ist und daheralarm_handler(1)
ein Aufruf einer Funktion über einen Funktionszeiger.Bisher habe ich gezeigt, dass die Verwendung einer
SignalHandler
Variablen relativ einfach ist, solange Sie den richtigen Werttyp zuweisen können - genau das bieten die beiden Signalhandlerfunktionen.Nun kommen wir zurück zu der Frage, in welcher
signal()
Beziehung die beiden Erklärungen zueinander stehen.Sehen wir uns die zweite Erklärung an:
Wenn wir den Funktionsnamen und den Typ wie folgt geändert haben:
Sie hätten kein Problem damit, dies als eine Funktion zu interpretieren, die ein
int
und eindouble
als Argumentedouble
annimmt und einen Wert zurückgibt (oder? Vielleicht sollten Sie sich nicht ein Bild machen, wenn dies problematisch ist - aber vielleicht sollten Sie vorsichtig sein, wenn Sie Fragen so hart stellen wie dieser, wenn es ein Problem ist).Anstatt a zu sein
double
, nimmt diesignal()
Funktion aSignalHandler
als zweites Argument und gibt eins als Ergebnis zurück.Die Mechanik, mit der das auch behandelt werden kann als:
sind schwierig zu erklären - also werde ich es wahrscheinlich vermasseln. Diesmal habe ich die Parameternamen angegeben - obwohl die Namen nicht kritisch sind.
Im Allgemeinen ist der Deklarationsmechanismus in C so, dass, wenn Sie schreiben:
dann, wenn Sie schreiben
var
, repräsentiert es einen Wert des Gegebenentype
. Beispielsweise:Im Standard
typedef
wird als Speicherklasse in der Grammatik behandelt, eher wiestatic
undextern
sind Speicherklassen.bedeutet, dass, wenn Sie eine Variable vom Typ
SignalHandler
(z. B. alarm_handler) sehen, die wie folgt aufgerufen wird:Das Ergebnis hat
type void
- es gibt kein Ergebnis. Und(*alarm_handler)(-1);
ist eine Anrufungalarm_handler()
mit Argument-1
.Also, wenn wir erklärt haben:
es bedeutet, dass:
stellt einen leeren Wert dar. Und deshalb:
ist gleichwertig. Jetzt
signal()
ist es komplexer, weil es nicht nur a zurückgibtSignalHandler
, sondern auch sowohl ein int als auch ein aSignalHandler
als Argumente akzeptiert :Wenn Sie das immer noch verwirrt, bin ich mir nicht sicher, wie ich Ihnen helfen soll - es ist mir immer noch auf einigen Ebenen rätselhaft, aber ich habe mich daran gewöhnt, wie es funktioniert, und kann Ihnen daher sagen, wenn Sie noch 25 Jahre dabei bleiben oder so, es wird für Sie zur zweiten Natur (und vielleicht sogar ein bisschen schneller, wenn Sie klug sind).
quelle
extern void (*signal(int, void(*)(int)))(int);
bedeutet, dass diesignal(int, void(*)(int))
Funktion einen Funktionszeiger zurückgibtvoid f(int)
. Wenn Sie einen Funktionszeiger als Rückgabewert angeben möchten , wird die Syntax kompliziert. Sie müssen den Rückgabewerttyp links und die Argumentliste rechts platzieren , während Sie die Mitte definieren. In diesem Fall nimmt diesignal()
Funktion selbst einen Funktionszeiger als Parameter, was die Sache noch komplizierter macht. Eine gute Nachricht ist, wenn Sie diese lesen können, ist die Macht bereits bei Ihnen. :).&
vor einem Funktionsnamen zu verwenden? Es ist völlig unnötig; sogar sinnlos. Und definitiv nicht "old school". Die alte Schule verwendet schlicht und einfach einen Funktionsnamen.Ein Funktionszeiger ist wie jeder andere Zeiger, zeigt jedoch auf die Adresse einer Funktion anstelle der Adresse von Daten (auf Heap oder Stack). Wie jeder Zeiger muss er korrekt eingegeben werden. Funktionen werden durch ihren Rückgabewert und die Arten von Parametern definiert, die sie akzeptieren. Um eine Funktion vollständig zu beschreiben, müssen Sie ihren Rückgabewert angeben und der Typ jedes Parameters wird akzeptiert. Wenn Sie eine solche Definition eingeben, geben Sie ihr einen "Anzeigenamen", der das Erstellen und Verweisen von Zeigern mit dieser Definition erleichtert.
Nehmen wir zum Beispiel an, Sie haben eine Funktion:
dann das folgende typedef:
kann verwendet werden, um auf diese
doMulitplication
Funktion zu verweisen . Es wird einfach ein Zeiger auf eine Funktion definiert, die einen float zurückgibt und zwei Parameter vom Typ float akzeptiert. Diese Definition hat den freundlichen Namenpt2Func
. Beachten Sie, dasspt2Func
dies auf JEDE Funktion verweisen kann, die einen Float zurückgibt und 2 Floats aufnimmt.So können Sie einen Zeiger erstellen, der wie folgt auf die Funktion doMultiplication verweist:
und Sie können die Funktion mit diesem Zeiger wie folgt aufrufen:
Dies macht eine gute Lektüre: http://www.newty.de/fpt/index.html
quelle
pt2Func myFnPtr = &doMultiplication;
anstattpt2Func *myFnPtr = &doMultiplication;
wiemyFnPtr
bereits ein Zeiger.myFunPtr
ist bereits ein Funktionszeiger, also benutzept2Func myFnPtr = &doMultiplication;
Ein sehr einfacher Weg, um typedef des Funktionszeigers zu verstehen:
quelle
cdecl
ist ein großartiges Werkzeug zum Entschlüsseln seltsamer Syntax wie Funktionszeigerdeklarationen. Sie können es auch verwenden, um sie zu generieren.In Bezug auf Tipps, wie komplizierte Deklarationen für zukünftige Wartungsarbeiten (von Ihnen selbst oder anderen) einfacher zu analysieren sind, empfehle ich,
typedef
kleine Stücke zu erstellen und diese kleinen Teile als Bausteine für größere und kompliziertere Ausdrücke zu verwenden. Beispielsweise:eher, als:
cdecl
kann dir bei diesem Zeug helfen:Und genau so habe ich dieses verrückte Durcheinander oben erzeugt.
quelle
Ausgabe davon ist:
22
6
Beachten Sie, dass für die Deklaration beider Funktionen derselbe math_func-Definierer verwendet wurde.
Der gleiche Ansatz von typedef kann für externe Strukturen verwendet werden (unter Verwendung von sturuct in einer anderen Datei).
quelle
Verwenden Sie typedefs, um kompliziertere Typen zu definieren, dh Funktionszeiger
Ich nehme das Beispiel der Definition einer Zustandsmaschine in C.
Jetzt haben wir einen Typ namens action_handler definiert, der zwei Zeiger verwendet und ein int zurückgibt
Definieren Sie Ihre Zustandsmaschine
Der Funktionszeiger auf die Aktion sieht aus wie ein einfacher Typ, und typedef dient in erster Linie diesem Zweck.
Alle meine Ereignishandler sollten jetzt dem von action_handler definierten Typ entsprechen
Verweise:
Expert C Programmierung von Linden
quelle
Dies ist das einfachste Beispiel für Funktionszeiger und Funktionszeigerarrays, die ich als Übung geschrieben habe.
quelle