mikrocontroller.net

Forum: Compiler & IDEs Befehlsinterpreter in C


Autor: W. Nickel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich habe ein AVR mit der seriellen Schnittstelle an einem PC hängen. Vom 
PC kommen unterschiedliche Befehle mit Parametern. Die Liste möglicher 
Befehle (bis jetzt insgesamt 67) unterbringe ich in einem Array aus 
Strings. Ferner erkenne ich die Befehle in einer Schleife mit strcmp. 
Der Schleifenzähler ist dann der Index des Befehls. Bis hierhin ist 
alles elegant und funktionsfähig. Jetz muss ich die Befehle ausführen.
Gibt es in C (WinAVR-gcc) eine Möglichkeit ohne switch und 67 x case 
auszukommen?

Also vielleicht 67 Funktionen mit gleichen Namen (z.B: 
befehlsausführung(1), befehlsausführung(2) ), die nur an den Parametern 
unterschieden werden. Dann kann ich im Hauptprogramm schreiben 
befehlsausführung(Index_des_Befehls); und schon wird die richtige 
Funktion angesprungen. Ich glaube, das heisst "Überladen"?

Oder ein Array aus Funktionen:
int befehlsausführung() [];
befehlsausführung()[1] { Code; };
Und Aufruf:
befehlsausführung()[Index_des_Befehls];

Autor: Karl heinz Buchegger (kbucheg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So eine ähnliche Funktionalität kannst du in der Tat
aufbauen.
Voraussetzung: Alle Funktionen haben die gleiche Signatur.

Du definierst dir einen Funktionspointer Datentyp.
(Ist nicht zwingend notwendig, vereinfacht die Sache aber).

Sagen wir mal deine Funktionen sind alle void - void

typedef void (*FunctPtr)( void );

(Wenn deine Funktionen einen int nehmen würden und einen
unsigned char liefern, würde das dann heissen:

   typedef unsigned char (*FuncPtr)( int );
)

FuncPtr ist der Name das Datentyps.

Weiters hast du noch du Funktionen:

void Funktion1()
{
  ...
}

void Funktion2()
{
  ...
}

Jetzt definierst du dir noch ein Array mit
Funktionspointern und initialisierst es
mit - Pointern auf die Funktionen.

FuncPtr Funktionen[] = { Funktion1, Funktion2 };

Um eine Funktion dann indirekt über einen
Funktionspointer aufzurufen, benutzt du
ganz normale Funktionssyntax.

   Funktionen[i]();

Autor: Karl heinz Buchegger (kbucheg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du kannst das ganze aber noch eleganter machen.

Im Moment hast du ein Array aus Strings.
Mach da ein Array aus Strukturen draus:

typedef void (*FuncPtr)(void);

struct Befehl {
  char Text[20];
  FuncPtr Funktion;
};

void FuncCopy()
{
   ...
}

void FuncPaste()
{
   ...
}

struct Befehl[] =
  {
    { "Copy", FuncCopy },
    { "Paste", FuncPaste }
  };

Auf die Art bleibt zusammen, was zusammen gehört.
Der Text, wie der Befehl heist und der Funktionspointer,
der für die Umsetzung des Befehls sorgt.

Deine Suchschleife lautet dann:

  FuncPtr Find( const char* String )
  {
    for( int i = 0; i < Anzahl_Befehle; ++i ) {
      if( strcmp( String, Befehl[i].Text ) == 0 )
        return Befehl[i].Funktion;
    }

    return NULL;
  }

Und verwendet wird es dann so:

   FuncPtr Func;
   char UARTBefehl[20];

   ...

   Func = Find( UARTBefehl );
   if( Func != NULL )
     Func();

Autor: W. Nickel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank, Karl heinz! Ich versuche jetzt mal die zweite Variante, da 
ich sowieso schon ein struct habe (ich habe dort u.a. den 
Parameter-Datentyp, min und max zu den Parametern usw.).

Autor: Cfrager (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Und wie sieht der typdef aus wenn der FuncPtr nicht auf eine void 
funktion zeigt sondern selbst vom Typ FunkPtr ist?
siehe Beispiel in der Anlage

Danke schonmal

Autor: Karl heinz Buchegger (kbucheg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Da hast du mich an meine Grenzen gebracht.
Das krieg ich auch nicht hin. Wenn ich mich
richtig erinnere gibt es aber dafür eine Lösung.
Hab ich mal vor Jahren in comp.lang.c gesehen.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Man kann sich nicht mit sich selbst definieren.

Wie wärs so:
typedef void (*func)(void);

typedef func (*ffunc)(void);


ffunc test1( void )
{
  return (ffunc) 0x1234;
}

void test( void )
{
  ffunc fp;

  fp = test1();

  for(;;)
    fp = (ffunc)fp();

}


Peter

Autor: Cfrager (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Leider nein.
Alle benutzten Compiler melden zwar OK (0 Error, 0 Warning).
Das erzeugte Programm ist aber nicht lauffähig, weil es sich immer 
wieder selbst aufruft bis zum PC: "Stack Overflow" bzw. AVR: "Watchdog 
Reset".
Getestet ohne die for(;;) Schleife.

Hat noch jemand andere Ideen?

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Cfrager wrote:

> Das erzeugte Programm ist aber nicht lauffähig, weil es sich immer
> wieder selbst aufruft bis zum PC: "Stack Overflow" bzw. AVR: "Watchdog
> Reset".
> Getestet ohne die for(;;) Schleife.


Die for-Schleife muß natürlich sein, Du willst doch die Funktion des 
Rückgabewertes aufrufen, erst nachdem die vorherige beeendet wurde.
Anders gehts nicht.


Die Funktionen dürfen sich nicht selber aufrufen, das wäre ja eine 
Rekursion ohne Abbruchbedingung. Da muß unbedingt der Stack überlaufen.
Und dann wäre ja ein Funktionspointer als Returnwert völlig überflüssig 
(never reached).


Peter

Autor: Cfrager (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich weiß auch nicht was der Compiler sich da zusammenreimt.
Alleine die Zeile "fp = test1();" reicht für den ungewollten Endlos 
Betrieb aus, obwohl das doch nur die Initialisiereung seien sollte.
Erst der folgende Aufruf "fp = (ffunc)fp();" sollte dann diese Funktion 
genau einmal ausführen und als Ergebnis die nächste auszuführende 
Funktion liefern
- ggf. auch sichselbst.

Joe

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Cfrager wrote:

> Alleine die Zeile "fp = test1();" reicht für den ungewollten Endlos
> Betrieb aus, obwohl das doch nur die Initialisiereung seien sollte.

Die Zuweisung 0x1234 ist natürlich nur ein Platzhalter.
Die mußt Du also durch die Adresse einer existierenden Funktion 
ersetzen.


Ich habs nur deshalb gemacht, um den erzeugten Assembler anzusehen und 
der sieht gut aus.


Peter

Autor: Karl heinz Buchegger (kbucheg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So wies aussieht, gehts ohne cast überhaupt nicht.
Mir fällt aich nichts besseres ein.

Das einzige as ich anders machen würde:

Anstatt der 2 typedefs würde ich die Funktion
einen void Pointer zurückliefern lassen:

typedef void* (*ffunc)(void);

void* test1( void )
{
  return run;
}

void* run( void )
{
  return run;
}

void test( void )
{
  void* fp;
  fp = test1();

  for(;;)
    fp = (ffunc)fp();
}

Der muss zwar auch gecastet werden, aber anders als mit
dem ursprünglichen Funktionspointer Rückgabewerte muss
er gecastet werden. Mit dem ursprünglichen func-Pointer
könne man theoretisch Schindluder treiben, wenn man ihn nicht
entsprechend zurechtcastet.


Autor: Karl heinz Buchegger (kbucheg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was mir gerade einfällt:
Ich würde da wahrscheinlich eine Zwischenfunktion
benutzen:
ffunc Call( ffunc function )
{
  return (ffunc) function();
}

um den cast zu verbergen.

Die Schleife von oben würde dann so aussehen:
   for( ;; )
     fp = Call( fp );

und so ist der cast erst mal weg aus der direkten Sichtlinie.
Wenn man die Funktion noch inlinen lässt, hat man auch keinen
Runtime-Penalty.

Autor: Cfrager (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sorry für die verzögerte Reaktion, aber...
das ist nicht was ich meinte/wollte.

Bis ca. 2002 - "alte Compiler" PC: Borland C  80x1: Keil-C
hat folgende Variante erfolgreich  funktioniert:
void* start(void);  // Prototyp
void* run(void);  // Prototyp
void* ende(void;  // Prototyp

void* (*fp)(void);  // globaler Zeiger auf Funktionen

void* start(void){
  return run;  // damit gehts weiter 1)
}
void* run(void){
  return ende;  // damit gehts weiter 1)
}
void* ende(void){
  return ende;  // fertig, hier bleiben  1)
}

void test(void){
  fp=start;    // initialisieren, noch nicht ausführen
  for(;;)
    fp=(*fp)();  // ausführen start, run, ende, ende,.... 2)
}

Mit "neueren Compilern" PC: MS Visual  AVR: IAR-C (strengere Typprüfung)
kamen dann die "Warnings" und "Errors" etwa so:
1) return value type does not match the function type
2) a value of type "void *" cannot be assigned to an entity of type 
"void *(*)()"

Mangels besserer C-Kenntnisse bin ich dann auf 'emun', 'switch' und 
Tabellen -wie im oberen Lösungsvorschlag- ausgewichen.
Ich hatte gehofft mir kann hier jemand mit einer "sauberen C 
Formulierung"
helfen.

Joe

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Es gibt keine 'saubere C Formulierung'. Mindestens ein
cast ist immer nötig. Und genau das sagt dir der Compiler
mit den Fehlermeldungen.
Im Grunde drehen sich beide Fehlermeldungen um die selbe
Sache: Die Datentypen sind nicht kompatibel. Zumindest nicht
auf dem Papier für den Compiler. Da du aber weist, dass alles
soweit richtig ist und stimmt, musst du den Compiler mit
einem cast überstimmen.

@Jörg
Jörg, du kennst den C Standard besser als ich. Früher malö
war es legal einen beliebigen Pointertyp auf einen void*
zuzuweisen. Ist das mit C99 gefallen und an C++ angeglichen worden?

Autor: arc (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Methode mit den void-Zeigern würde ich vermeiden, da nicht 
garantiert ist das Zeiger auf Objekte und Zeiger auf Funktionen die 
gleiche Größe haben.

In C++ würde man einfach eine Proxy-Klasse definieren
struct FuncPtrProxy;
typedef struct FuncPtrProxy (*FuncPtr)(void);
struct FuncPtrProxy {
  FuncPtr fp;
  FuncPtrProxy(FuncPtr p) { fp = p; }
  operator FuncPtr() { return fp; }  
};

FuncPtrProxy stop() {
  return stop;
}

FuncPtrProxy start() {
  return stop;
}

In C wird es etwas umständlicher...
struct FuncPtrProxy;
typedef struct FuncPtrProxy (*FuncPtr)(void);
struct FuncPtrProxy {
  FuncPtr fp;
};
FuncPtrProxy stop() {
  FuncPtrProxy next = { stop };
  return next;
}

FuncPtrProxy start() {
  FuncPtrProxy next = { stop };
  return next;
}

int main( void )
{
  FuncPtrProxy f = { start };
    
  for (;;) {
    f = f.fp();
  }
} 


Autor: Marko B. (glagnar)
Datum:
Angehängte Dateien:

Bewertung
1 lesenswert
nicht lesenswert
Ich hab sowas mal gemacht, siehe Anhang. Braucht so gut wie kein RAM. 
Einfach erweiterbar. Parameter an die Unterfunktionen werden als 
argc/argv übergeben.

Autor: W. Nickel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also, das Programm von Marko B. ist genau das, was ich brauche. Ich habe 
es auch ohne Veränderung zum laufen gebracht, allerdings nur auf dem PC 
(gcc 3.4.4 unter Cygwin). Die Implementierung für den AVR ist etwas 
schwieriger, weil
1. das Array in den Programmspeicher muss: const command_t PROGMEM 
mycmds[] = { ... };,
2. deshalb die strcmp_P anstelle der strcmp verwendet werden muss,
3. die Daten über Funktionen, wie pgm_read_byte usw. gelesen werden 
müssen und
4. die entsprechende Funktion irgendwie anders aufgerufen werden muss. 
Aber wie?

Autor: W. Nickel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
PS. Mit kleineren Befehlssätzen, die nicht den RAM füllen funktioniert 
das Programm auch auf dem AVR.

Autor: W. Nickel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich habe es gelöst. Eine Möglichkeit ist es, die Adresse der Funktion 
mittels pgm_read_word rauszulesen und an einen Zwischenpointer zu 
übergeben, der dann aufgerufen wird:

fptr temppointer;
...
temppointer = (fptr)pgm_read_word(&mycmds[i].function);
temppointer(sargc, sargv);
...

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.