Forum: Mikrocontroller und Digitale Elektronik Anfängerfrage: switch-case vs. else-if


von sippy (Gast)


Lesenswert?

Hallo,
ich habe ein rel. grosses Programm (Für Hobby-Verhältnisse ;-)  ) in 
welchem ein int auf (momentan noch) 53 Zustände geprüft werden soll.

Das int ist dabei ein Indexbyte (einfach nur das erste Byte der 
Nachricht) einer 8-Byte UART Nachricht welche ich an meinem µC empfange.

Momentan habe ich die Zuständen (0x00 bis 0x34) als define deklariert 
und frage das int dann im Programm ab:
1
     if (empfangenes_Index_Byte == PARAMETER001)
2
{
3
   ....
4
}
5
else if (empfangenes_Index_Byte == PARAMETER002)
6
{
7
   ....
8
}
9
else if (empfangenes_Index_Byte == PARAMETER003)
10
{
11
   ....
12
}
usw. usf.

Nun hab ich mir überlegt ob ich das ganze nicht als switch-case umbaue, 
ich kenne aber nicht den exakten Unterschied zwischen den beiden 
Abfragen, was wäre für mich besser?


Anderseits, und hier beginnt mein eigentliches Problem, habe ich mir 
auch schon überlegt das ganze komplett anderst zu machen, die 53 
Zuständen werden später vlt. noch erweitert und bereits jetzt ist das 
ganze ein ziemlicher Spaghetti-code.

In den jeweiligen If-Bedingungen werden zum einen Funktionen aufgerufen 
die in allen Bedingungen vorkommen und z.T. wird aber auch eine 
individuelle Bitmanipulation vorgenommen.
Hier mal ein Beispiel:
1
else if (empfangenes_Byte == PARAMETER021)
2
{
3
   n = Ueberpruefe_Laenge(n);
4
   i = Ueberpruefe_CRC(i);
5
   if( n == OK)
6
   {
7
      if (i == OK)
8
      {
9
         sende(Stellung_Weiche_3);
10
      }
11
   }
12
}

Parameter 21 hier z.B. soll die Stellung einer Eisenbahnweiche zurück 
senden. Länge und CRC-Prüfung ist überall drin, aber eben z.B. das sende 
der Weichenstellung ist individuell, in einer anderen Bedingung schalte 
ich z.B. LED's ein (Hier könnte also "alles" stehen: Parameter lesen, 
Zustände setzen, weitere Funktionen aufrufen...)


Gibt es hierfür nicht "irgendetwas" anderes als eben else-if oder 
switch-case abfragen? Wie machen das die Profis, wenn ich keine 53 
sondern 1000 mögliche Zustände habe, dann kann ich doch nicht eine 
switch-case mit 1000 Abfragen ausführen?
Da leidet ja die Übersicht extrem darunter und schon jetzt bei meinen 53 
Abfragen muss ich ewig hoch und runter scrollen bis ich mal an der 
Stelle bin die ich gerade suche!

Mir ist das ganze eben viel zu unübersichtlich, auch auf zukünfige 
Sicht, welche Möglichkeiten hätte ich ausser else-if und/oder 
switch-case, bzw. wo genau liegt denn der Unterschied zwischen den 
beiden Abfragen?

von Bernd G. (bege)


Lesenswert?

Hallo,

wie du oben schon selbst schreibst, sind 53 if-else Verschachtelungen 
nicht gerade leserlich.

Der wesendliche Unterschied zwischen if-else und switch-case ist, daß 
man bei if-else Konstrukten beliebige boolsche Ausdrücke prüfen kann, 
bei switch-case wird immer nur die Variable im SWITCH mit exakt dem Wert 
von CASE verglichen. Konstrukte wie z.B. if ((var > 10) && (var <20)) 
sind als switch-case eher umständlicher (Stichwort Fall-Through).

Der Vorteil von switch-case ist, daß der Compiler den Code viel besser 
optimieren kann wie das bei einzelnen if-else Statements möglich ist.
Der Grund dafür ist, daß er ja weiß, daß immer die selbe Variable 
geprüft wird, und daß der Vergeich immer mit einem eindeutigen 
konstanten Wert erfoglt und nicht von anderen Bedinungen abhängt.

Der Compiler kann bei größeren switch-case Konstrukten optimieren, indem 
er das ganze als indizierte Sprungtabelle implementieren kann. Das geht 
bei if-else Anweisungen nicht.

Unter den genannten Gesichtspunkten (Code-Übersichtlichkeit, 
Compiler-Optimierung) würde ich für deinen Fall das switch-case 
Konstrukt bevorzugen.

Gruß Bernd

von Klaus W. (mfgkw)


Lesenswert?

Dann mach doch Länge und CRC vor der Verzweigung, dann ein
1
switch( empfangenes_Index_Byte )
2
{
3
    case PARAMETER001:
4
        ...
5
        break;
6
    case PARAMETER002:
7
        ...
8
        break;
9
    case PARAMETER003:
10
        ...
11
        break;
12
}

Ansonsten:
- PARAMETER001 etc. sind ziemlich nichtssagende Namen.
- Anstatt der ganzen #define PARAMETER ist eine enum sicher eleganter:
1
   enum
2
   {
3
       PARAMETER001,
4
       PARAMETER002,
5
       // etc.
6
   }

von Condi (Gast)


Lesenswert?

Switch Case ist hier das bessere. Alternativ eine Funktion schreiben und 
diese in einer Lookuptabelle hinterlegen(wie bei den Interruptvektoren).

von Klaus W. (mfgkw)


Lesenswert?

Wenn Dir das switch noch zu lang ist, gibt es u.U. die Möglichkeit,
eine entsprechend große Tabelle mit Zeigern auf Funktionen anzulegen,
und das empfangenes_Index_Byte als Index in diese Tabelle zu verwenden,
um die jeweils passende Funktion aufzurufen. Der Inhalt je eines
case-Zweiges geht dann in je eine Funktion.
Das geht aber nur, wenn die Funktionen auf eine einheitliche
Parameterliste zu kriegen sind (was je nach Fall mal besser oder
schlechter oder gar nicht geht).
Wenn es geht, ist es aber unschlagbar schnell und elegant.
(Nur wer keine Funktionszeiger mag, wird es hassen).

von Klaus W. (mfgkw)


Lesenswert?

Condi schrieb:
> Switch Case ist hier das bessere. Alternativ eine Funktion schreiben und
> diese in einer Lookuptabelle hinterlegen(wie bei den Interruptvektoren).

genau das meinte ich

von Andreas K. (derandi)


Lesenswert?

Das switch-Case-Zeug ist aber wunderbar schnell, man wirft eine Zahl 
rein und kommt direkt bei nur einer Möglichkeit raus.
Bei else-if geht er natürlich alle Abfragen durch bis er zur relevanten 
ankommt, das dauert seine Weile.
else-if benutze ich nur wenn ich diverse Bedingungen miteinander 
verknüpfe und mehr als nur eine Zahl habe.

Eine der beiden Möglichkeiten wirst du wohl benutzen müssen, aber man 
kann es ja in eine Funktion packen und die in eine andere Datei, dann 
bleibt das auch übersichtlich.

In deinem zweiten beispiel könntest du auch beide Bedingungen in einer 
If-Abfrage bearbeiten:
1
if(n == OK && i == OK)
2
 machwas();

von avr (Gast)


Lesenswert?

Gut ist bei switch case die Variant mit default.

Dort sammeln sich dann die Fälle die man nicht explizit
aufgeführt hat. Gut auch um Fehler abzufangen.

avr

von Stefan (Gast)


Lesenswert?

Wenn Du z.B. 1000 verschiedenen Zustände hast, dann wird es auch immer 
auf 1000 Überprüfungen hinauslaufen... ob nun mit if-else oder 
switch-case!
Um die Übersichtlichkeit zu erhöhen, könntest Du aber die verschiedenen 
Funktionen, die ausgeführt werden sollen, in logische Gruppen oder 
Befehlskategorien zusammenfassen.
Z.B. Aktion ausführen, Status abfragen, etc.
Innerhalb der Befehlskategorien werden dann Untergruppen aktiviert:
z.B. Aktionen ausführen -> Weiche x stellen, LED y anmachen...

von ... .. (docean) Benutzerseite


Lesenswert?

Stefan schrieb:
> Wenn Du z.B. 1000 verschiedenen Zustände hast, dann wird es auch immer
> auf 1000 Überprüfungen hinauslaufen... ob nun mit if-else oder
> switch-case!


Falsch wie oben schon geschrieben, das glit nur beim ifelse
beim Switch wird einmal geprüft und dann passend gesprungen

von Stefan (Gast)


Lesenswert?

>Stefan schrieb:
>> Wenn Du z.B. 1000 verschiedenen Zustände hast, dann wird es auch immer
>> auf 1000 Überprüfungen hinauslaufen... ob nun mit if-else oder
>> switch-case!

>Falsch wie oben schon geschrieben, das glit nur beim ifelse
>beim Switch wird einmal geprüft und dann passend gesprungen

Im Programm-Code wirst Du aber 1000 Überprüfungen finden... da kommst 
auch Du nicht drum herum! Und das ist unübersichtlich. Ich habe nur 
versucht darzustellen, wie man das übersichtlicher gestalten könnte!

von MeinerEiner (Gast)


Lesenswert?

Dachte, bei switch wird das Sprungziel abhängig vom Wert der Variable 
bestimmt? Bzw. der zu prüfende Wert wird als Offset für die Zieladresse 
verwendet?

von icke (Gast)


Lesenswert?

>Falsch wie oben schon geschrieben, das glit nur beim ifelse
>beim Switch wird einmal geprüft und dann passend gesprungen
Wie soll das denn gehen bitteschön, die einsprungpunkte sind doch nicht 
gleichmäßig verteilt.

Wenn du es nicht glaubst Dann schau dir mal den Assemblercode an.

von Karl H. (kbuchegg)


Lesenswert?

... ... schrieb:
> Stefan schrieb:
>> Wenn Du z.B. 1000 verschiedenen Zustände hast, dann wird es auch immer
>> auf 1000 Überprüfungen hinauslaufen... ob nun mit if-else oder
>> switch-case!
>
>
> Falsch wie oben schon geschrieben, das glit nur beim ifelse
> beim Switch wird einmal geprüft und dann passend gesprungen

Schreib bitte dem Compiler nicht vor, wie er einen switch-case zu 
implementieren hat. Gefordert ist, dass bei einem switch-case die 
richtige Alternative ausgeführt wird. Wie der Compiler das macht ist 
seine Sache. Wenn die Werte günstig liegen, kann er dazu eine 
Spruntabelle nehmen, wenn sie ungünstig liegen kann er dazu eine ganz 
normale if-else Prüfkette aufbauen.

von (prx) A. K. (prx)


Lesenswert?

Eine klassische if-else Kette in der Implementierung von switch Befehlen 
wird man allenfalls bei sehr wenig Werten finden. Wenn sich eine Tabelle 
nicht lohnt, wird ein Compiler oft einen Vergleichsbaum verwenden, d.h. 
die Laufzeit wächst logarithmisch statt linear.

Sowas ist manuell einigermassen schwierig.

von Karl H. (kbuchegg)


Lesenswert?

> Wie machen das die Profis, wenn ich keine 53
> sondern 1000 mögliche Zustände habe

Sie überlegen sich zum Beispiel, wie sie die Logik in eine Tabelle 
pressen können.
Das kann seine eine Tabelle von Funktionspointern. Das kann aber auch 
sein eine Tabelle in der zb eine bestimmte Nummer für eine bestimmte 
Aktion steht und wieder eine andere Nummer für ein Argument, oder 
Kombinationen aus beidem.
1
struct Action
2
{
3
  uint16_t CommandNr;
4
  uint16_t ActionNr;
5
  uint16_t VariableNr;
6
};
7
8
struct Action actions[]
9
{
10
  ....
11
  { PARAMETER021, actionSend, Stellung_Weiche_3 },
12
  ....
13
};

Wird ein Byte empfangen, geht eine Schleife die Aktionen durch (oder 
benutzt den empfangenen Parameter als Index ins Array) und holt sich die 
auszuführende Aktion (hier actionSend) und worauf diese Aktion 
ausgeführt werden soll (hier Stellung_Weiche_3)

Eine neue Aktion einzuführen ist dann einfach nur ein neuer Eintrag in 
dieser Tabelle.

Oder eine andere Möglichkeit:
Anstatt nur einem Byte übertragen sie 2 Bytes um von der Gegenstelle 
etwas anzufordern. Das erste Byte ist zb ein Kommandocode (zb für 
senden) und das zweite Byte ist ein Code womit diese Aktion ausgeführt 
werden soll.

Deine 53 Zustände schrumpfen dann wahrscheinlich auf 3 oder 4 Aktionen 
und 10 oder 12 Objektbezeichner zusammen.

Weil wir heuer 40 Jahre Mondlandung haben: So haben zb. die Astronauten 
mit ihrem Bordcomputer kommuniziert: Es gab ein Verb-Noun System. Jedes 
Verb war einfach nur eine Zahl und stand für eine Aktion.
zb 05  .... zeige oktal
   06  .... zeige dezimal
       ....
Jedes Noun war die Bezeichnung für ein Objekt (diverse Anzeigeformen 
wurden durch unterschiedliche Nouns repräsentiert)
   09  .... Alarm Codes
   36  .... current Time
       .... aktuelle Position
       .... Zeit bis zur nächsten Zündung
            etc

Wollte ein Astronaut wissen, wie spät es ist (gemessein in Mission Time) 
dann drückte er
 Taste "Verb" Taste "0" Taste "6" Taste "Noun" Taste "3" Taste "6" Taste 
"Enter"  oder kurz V06N36E
und der Computer verstand das als "aktuelle Zeit anzeigen"

Als während der Landung am Computer das Warnlicht für "Program Alarm" 
aufleuchtete, drückte Aldrin V05N09E, der Computer zeigte auf seiner 
7-Seg Anzeige 1202 an. Aldrin fragte in Houston nach und von dort kam 
das "GO" - die Landung fortsetzen, wir können den Fehler ignorieren.

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Eine klassische if-else Kette in der Implementierung von switch Befehlen
> wird man allenfalls bei sehr wenig Werten finden. Wenn sich eine Tabelle
> nicht lohnt, wird ein Compiler oft einen Vergleichsbaum verwenden, d.h.
> die Laufzeit wächst logarithmisch statt linear.

Kann natürlich auch sein.
Auf jeden Fall ist die Aussage: "beim Switch wird einmal geprüft" so 
nicht richtig.

von Tobias K. (kurzschluss81)


Lesenswert?

Noch ein Tip am Rande.
Ich würde auf die Defines verzichten und die Zuodnung der Ziffern zu 
einerm Ausdruch über ein Enum Type machen.
1
typedef enum
2
{
3
   Ausdruck_1,
4
   Ausdruck_2,
5
   Ausdruck_3,
6
   ...
7
}Switch_Befehle;
8
9
Switch_Befehle abfrage_befehl_1;

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.