C-Codierung Design - Funktionszeiger?

24

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 neue Eingaben vorliegen. Bei Eingabe wird eine Funktion entsprechend aufgerufen. So zum Beispiel , wenn ich Eingang eines A auf stdin, läuft der PIC eine Funktion wie function_Astatt function_Bdem aufgerufen wird , wenn ich Eingabe eines B.

Wenn der PIC mit der Funktion fertig ist, möchte ich, dass die neue Eingabe an die Funktion gesendet wird. Wenn Sie also A drücken, wird der RS232-Sender geöffnet. Ab diesem Moment wird jeder Eingang ü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 sind Sie nicht mehr an der Textbearbeitung beteiligt, sondern durchsuchen eine Liste von Dateien. Das bedeutet, dass das Drücken von Nach oben und Nach unten etwas anderes bedeutet als in der Textbearbeitungsumgebung.

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

  • Die mainFunktion ruft eine Funktion wiefunction_A
  • function_AÄndert eine globale Variable function_addrin den Adresszeiger der Funktionin_function_A
  • mainRuft die Funktion von diesem Moment an auf, function_addrwenn neue Eingaben vorliegen.

Ich brauche also eine mainFunktion, die prüft, ob function_addrNull ist. Wenn ja, sollte eine 'normale' Funktion aufgerufen werden function_A. Ist dies nicht der function_addrFall , muss die Funktion at aufgerufen werden. Ich brauche auch einen, function_Ader den function_addrin einen Zeiger ändert in_function_A.

Hinweis: Wenn die Dateisystemfunktion geschlossen werden is_function_Asoll , sollte nur function_addrauf 0 geändert werden .

Im Grunde ist meine Frage, wie ich kann

  • Ruft die Adresse einer Funktion ab (und speichert sie in einer Variablen)
  • Rufen Sie eine Funktion an einer angegebenen Adresse auf

quelle
6
Off topic. Gehört auf stackoverflow.com.
user207421
Ein State-Machine-Ansatz ist für mich viel weniger riskant als der Umgang mit Funktionszeigern. Ihr Eingabebyte wird an eine Zustandsmaschinenstruktur übergeben (kann so einfach wie ein Schalter sein), die in Abhängigkeit von der Zustandsvariablen oder den Zustandsvariablen zu verschiedenen Codebits springt. Außerdem ist dir nicht ganz klar, woher dein Stdin kommt (nicht, dass es wirklich wichtig ist, aber ich bin neugierig).
Adam Lawrence
9
@EJP Ich bin anderer Meinung. Nur weil es eine Überlappung gibt, heißt das nicht, dass es sich auf der einen oder der anderen Seite befinden muss. Es werden Fragen im Zusammenhang mit dem Design und der Programmierung von Embedded-Systemen auf niedriger Ebene gestellt, die an beiden Stellen thematisiert zu sein scheinen.
Kortuk
Zustandsautomaten können auf der Indirektion von Funktionen basieren: Der Aufruf der Zustandsübergangsfunktion gibt eine neue Zustandsübergangsfunktion zurück.
Kaz
1
Lassen Sie uns diese Diskussion hier haben .

Antworten:

21

Eine Funktion

int f(int x){ .. }

Ruft die Adresse einer Funktion ab (und speichert sie in einer Variablen)

int (*fp)(int) = f;

Rufen Sie eine Funktion an einer angegebenen Adresse auf

int x = (*fp)( 12 );
Wouter van Ooijen
quelle
3
+1, offensichtlich in der C- und ASM-Welt, aber nicht so offensichtlich für diejenigen, die mit OO-Sprachen begonnen haben.
Anindo Ghosh
4
Der fp-Aufruf kann auch geschrieben werden als 'int x = fp (12);' die Lesbarkeit zu erhöhen.
Rev1.0
1
Ich muss zugeben, ich arbeite derzeit hauptsächlich in C # und Java und vermisse manchmal diese guten alten Funktionszeiger aus C. Sie sind gefährlich, wenn sie nicht genau richtig ausgeführt werden, und die meisten Aufgaben können auf andere Weise ausgeführt werden. Sie sind jedoch in den wenigen Fällen nützlich, in denen sie sich wirklich als nützlich erweisen.
ein Lebenslauf am
@ MichaelKjörling: Also wahr. Ich wechsle ständig zwischen eingebetteter C- und C # -Programmierung (selbst wenn ich ähnlichen Code für die Kommunikation zwischen Gerät und PC implementiere) und so mächtig C # auch sein mag, ich genieße C immer noch so sehr.
Rev1.0
2
fp(12)erhöht die Lesbarkeit nicht um (*fp)(12). Sie haben 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 verknüpft. Ein möglicher Grund für die Verknüpfung mit K & R C ist, dass in ANSI C die Deklaration fpmithilfe der Funktionssyntax möglich ist , sofern fpes sich um einen Parameter handelt: int func(int fp(double)) {}. Bei K & R C wäre das gewesen int func(fp) int (*fp)(double); { ... }.
Kaz
17

Während Wouters Antwort absolut richtig ist, ist dies vielleicht eher einsteigerfreundlich 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 wurde, ist es ratsam, eine NULL-Prüfung durchzuführen.

if (fp != NULL)
    fp(); /* with parameters, if applicable */
Rev1.0
quelle
8
+1 für die Nullprüfung, aber beachten Sie, dass der Compiler möglicherweise nicht garantiert, dass der Wert an der Adresse im RAM, fpdie gerade belegt wird, anfänglich NULL ist. Ich würde dies int (*fp)(int) = NULL;als kombinierte Deklaration und Initialisierung tun, um sicherzugehen - Sie möchten nicht zu einem zufälligen Speicherort springen, weil ein doofer Wert gerade da war. Dann weisen fpSie einfach wie in Ihrer Example()Funktion.
ein Lebenslauf am
1
Danke für den Kommentar, ich habe die NULL-Initialisierung hinzugefügt.
Rev1.0
4
@ 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).
Alok