C-Codierungsdesign - Funktionszeiger?

Ich habe einen PIC18F46K22 und programmiere ihn mit dem XC8-Compiler. Am Ende habe ich ein System wie ein PC mit stdinund stdout. In der Hauptschleife gibt es also eine Funktion, die prüft, ob es neue Eingaben gibt. Wenn eine Eingabe vorhanden ist, wird eine entsprechende Funktion aufgerufen. Wenn ich also zum Beispiel ein A stdineingebe, führt der PIC eine Funktion wie function_Aaus function_B, die aufgerufen wird, wenn ich ein B eingebe.

Wenn der PIC mit der Funktion fertig ist, möchte ich, dass die neue Eingabe an die Funktion gesendet wird. Wenn also A gedrückt wird, öffnet sich der RS232-Sender, von diesem Moment an wird jede Eingabe über RS232 gesendet. Am Ende ist das Projekt ein eigenständiger Texteditor. Wenn Sie also A drücken, wird das Dateisystem geöffnet. Von diesem Moment an bearbeiten Sie keinen Text mehr, sondern sehen sich eine Liste von Dateien an. Das bedeutet, dass das Drücken von Auf und Ab etwas anderes bedeutet als in der Textbearbeitungsumgebung.

Ich habe viel darüber nachgedacht, wie ich das in C programmieren kann. Ich habe mir das letzte Nacht ausgedacht und würde gerne wissen, ob es möglich ist und wenn ja, wie. Was ich tun möchte, ist:

  • Die mainFunktion ruft eine Funktion wie auffunction_A
  • function_Aändert eine globale Variable function_addrin den Adresszeiger der Funktionin_function_A
  • Ruft ab diesem Moment maindie Funktion auf , function_addrwenn eine neue Eingabe vorliegt.

Was ich also brauchen würde, ist eine mainFunktion, die prüft, ob function_addrNull ist. Wenn dies der Fall ist, sollte eine "normale" Funktion aufgerufen werden, z. B. function_A. Ist dies nicht der Fall, function_addrsollte die Funktion at aufgerufen werden. Ich brauche auch einen function_A, der den function_addrin einen Zeiger auf ändert in_function_A.

Hinweis: Wenn die Dateisystemfunktion geschlossen werden soll, is_function_Asollte sie einfach function_addrauf 0 geändert werden.

Also im Grunde ist meine Frage, wie kann ich

  • Holen Sie sich die Adresse einer Funktion (und speichern Sie sie in einer Variablen)
  • Rufen Sie eine Funktion an einer angegebenen Adresse auf
Off-Topic. Gehört auf stackoverflow.com.
Für mich ist ein State-Machine-Ansatz viel weniger riskant als der Umgang mit Funktionszeigern. Ihr Eingabebyte wird an eine Zustandsmaschinenstruktur (könnte so einfach wie ein Schalterfall sein) übergeben, die in Abhängigkeit von der Zustandsvariablen oder den Zustandsvariablen zu verschiedenen Codebits springt. Außerdem ist Ihnen nicht ganz klar, woher Ihre stdin kommt (nicht, dass es wirklich viel ausmacht, aber ich bin neugierig).
@EJP Ich bin anderer Meinung. Nur weil es eine Überschneidung gibt, heißt das nicht, dass es an der einen oder anderen Seite liegen muss. Es stellt Fragen im Zusammenhang mit dem Design und der Programmierung von Low-Level-Embedded-Systemen, die an beiden Stellen ein Thema zu sein scheinen.
Zustandsmaschinen können auf Funktionsindirektion basieren: Der Aufruf der Zustandsübergangsfunktion gibt eine neue Zustandsübergangsfunktion zurück.
@ Kortuk Nun , ich bin anderer Meinung. Es ist eine Frage der Computerprogrammierung und hat genau nichts mit EE zu tun, es sei denn, Sie möchten darstellen, dass EE Computerprogrammierung umfasst, was aus sehr langer Erfahrung keine Überzeugung ist, die ich teile. Es stellt sich auch die Frage, wo das entsprechende Know-how am stärksten konzentriert ist, und dass SO zweifelsfrei die Nase vorn hat. Hier statt dort zu fragen ist also im Grunde keine rationale Art der Befragung.
Lasst uns diese Diskussion hier führen .
Es kann hilfreich sein, sich Beispiele für Funktionszeiger in einem Buch wie en.wikibooks.org/wiki/C_Programming/… anzusehen.

Antworten (2)

Eine Funktion

int f(int x){ .. }

Holen Sie sich die Adresse einer Funktion (und speichern Sie sie in einer Variablen)

int (*fp)(int) = f;

Rufen Sie eine Funktion an einer angegebenen Adresse auf

int x = (*fp)( 12 );
+1, offensichtlich in der C- und ASM-Welt, aber nicht so offensichtlich für diejenigen, die mit OO-Sprachen begonnen haben.
Der fp-Aufruf kann auch geschrieben werden als 'int x = fp(12);' um die Lesbarkeit zu erhöhen.
Ich muss zugeben, da ich derzeit hauptsächlich in C# und Java arbeite, vermisse ich manchmal diese guten alten Funktionszeiger von C. Sie sind gefährlich, wenn sie nicht genau richtig gemacht werden, und die meisten Aufgaben können auf andere Weise erledigt werden, aber sie sind sicher nützlich , wenn sie sich wirklich als nützlich erweisen.
@MichaelKjörling: So wahr. Ich wechsle ständig zwischen eingebetteter C- und C#-Programmierung (implementiere sogar ähnlichen Code für die Kommunikation zwischen Gerät und PC) und so mächtig C# auch sein mag, C# macht mir immer noch viel Spaß.
@Michiael: Ich bin kein starker Java-Benutzer, aber können Sie die Funktion nicht einfach als Klassenmethode umschließen und diese Klasse (einen Zeiger auf) speichern?
@Wouter Das könntest du wahrscheinlich. Mein Punkt ist nicht, dass Sie etwas Ähnliches nicht tun können, sondern dass tatsächliche Zeiger in den wenigen Fällen, in denen dies nicht möglich ist, äußerst nützlich sein können, oder wenn das Gleiche auf andere Weise viel mehr Code erfordert. Mir fällt auf Anhieb kein gutes Beispiel ein, aber das hat wohl eher damit zu tun, dass ich heutzutage meistens dort programmiere, wo kein Bedarf besteht. Zeiger (insbesondere in einem ungeprüften Kontext) sind gefährlich und notorisch schwer richtig hinzubekommen, aber gelegentlich sind bloße Zeiger genau das Werkzeug, das Sie für die anstehende Aufgabe benötigen.
@MichaelKjörling: Mit C# können Sie Variablen vom Delegate-Typ (insbesondere die Typen Func<...> und Action<...>) mehr oder weniger so verwenden, wie C Funktionszeiger verwendet. Dies gilt insbesondere, wenn Sie das Delgate gleich einer statischen Methode setzen. (Bei nicht statischen Methoden verhalten sie sich nicht wie das Zeiger-auf-Member-Konstrukt von C++, sondern behalten stattdessen eine Referenz auf eine bestimmte Instanz bei). Auch mit C# können Sie, wenn Ihr Code mit vollem Vertrauen ausgeführt wird (wie Desktop-Apps), rohe Nicht-Funktionszeiger mit „unsicherem“ Code verwenden. Java verzichtet bewusst auf beide Features.
fp(12)erhöht nicht die Lesbarkeit über (*fp)(12). Sie haben eine unterschiedliche Lesbarkeit. (*fp)(12)erinnert den Leser daran, dass fpes sich um einen Zeiger handelt, und ähnelt auch der Deklaration von fp. Meiner Meinung nach ist dieser Stil mit alten Quellen wie X11-Interna und K&R C verbunden. Ein möglicher Grund, warum er mit K&R C verbunden sein könnte, ist, dass es in ANSI C möglich ist, fpmithilfe der Funktionssyntax zu deklarieren, ob fpein Parameter ist: int func(int fp(double)) {}. Bei K&R C wäre das gewesen int func(fp) int (*fp)(double); { ... }.
Es könnte sich lohnen, die Auswirkungen der Implementierung von Funktionszeigern auf einem Bare-Metal-Bild am unteren Ende zu diskutieren.
@Kortuk der Kontext der Frage ist ein PIC18F46K22, kaum ein Low-End-PIC. Aber auf 14-Bit-Kernen unterscheidet sich ein Funktionszeiger kaum von anderen Zeigern. Auf einem 12-Bit-Kern sind die Dinge kompliziert, aber ich bezweifle, dass man auf einem Chip mit einem 2-Level-Stack sowieso Funktionszeiger verwenden würde.
@WoutervanOoijen Vielen Benutzern ist nicht klar, dass sie möglicherweise etwas sehr Verrücktes tun. Wir hatten einige Low-End-Geräte mit Funktionszeigern, weil sie es vorher so gemacht hatten. Es gibt viel schönere Dinge als PIC18, es könnte sich nur lohnen, ein wenig zu erweitern, um die Auswirkungen auf Mikrochips einzubeziehen. Nur ein Vorschlag.

Während Wouters Antwort absolut richtig ist, ist dies vielleicht anfängerfreundlicher und ein besseres Beispiel für Ihre Frage.

int (*fp)(int) = NULL;

int FunctionA(int x){ 
    return x + x;
}

int FunctionB(int x){ 
    return x * x;
}

void Example(void) {
    int x = 0;

    fp = FunctionA;
    x = fp(3);  /* after this, x == 6 */

    fp = FunctionB;
    x = fp(3);  /* after this, x == 9 */
}

Wenn es nicht offensichtlich ist, dass dem Funktionszeiger tatsächlich eine Zielfunktionsadresse zugewiesen ist, ist es ratsam, eine NULL-Prüfung durchzuführen.

if (fp != NULL)
    fp(); /* with parameters, if applicable */
+1 für die Nullprüfung, aber beachten Sie , dass der Compiler möglicherweise nicht garantiert, dass der Wert an der Adresse im RAM, der fpzufällig belegt wird, anfänglich tatsächlich NULL ist. Ich würde es int (*fp)(int) = NULL;als kombinierte Deklaration und Initialisierung tun, um sicher zu sein - Sie möchten nicht zu einem zufälligen Speicherort springen, weil ein alberner Wert zufällig dort war. Dann weisen Sie einfach fplike in Ihrer Example()Funktion zu.
Danke für den Kommentar, ich habe die NULL-Initialisierung hinzugefügt.
@MichaelKjörling guter Punkt, aber wenn fpes sich um eine "globale Variable" handelt (wie im obigen Code), wird sie garantiert initialisiert NULL(ohne dies explizit tun zu müssen).