Forum: Compiler & IDEs Viele Entscheidungen übersichtlich ?


von peter dannegger (Gast)


Angehängte Dateien:

Lesenswert?

Oftmals kommt es ja zu großen Statemaschinen (switch-Anweisung), wo
hinterher viele Sachen wieder gemeinsam gemacht werden müssen.

D.h. hinter jedem case stehen ne Menge gleicher Sachen.

Damit sieht das ganze aber sehr unübersichtlich aus und man muß bei
Änderungen an vielen Stellen gleichzeitig ändern.


Z.B. ein Kommandointerpreter hat folgende Fälle:

1. Kommandos können ignoriert werden -> nur Puffer freigeben
2. Kommandos sind ausgeführt -> Bestätigung senden
3. Kommandos sind fehlerhaft -> Fehler melden
4. Kommandos können erst später ausgeführt werden -> nichts

Als einzige Lösung, um sowas übersichtlich zu gestalten ist mir die
do{}while(0) Schleife eingefallen.

Anbei mal ein Beispielprogramm dafür.


Ist das o.k. so vom Programmierstil her oder kann man das besser machen
?



Peter

von A.K. (Gast)


Lesenswert?

Die klassische Streitfrage strukturierter Programmierung: Inwieweit ist
"goto" möglicherweise erlaubt und wann ist das am Ende sogar
übersichtlicher als gängige Vermeidungsstrategien?

Ich jedenfalls finde ein "continue" das nicht zum direkt umgebenden
Block gehört schlimmer als ein Label mit "goto". Und grad die hier
präsentierte Variante vom "continue" und "return" betrachte ich als
eine verkrampfte Strategie zur scheinbaren Vermeidung von "goto", denn
das "continue" bezieht sich ja nicht auf den direkt umgebenden Block
("switch" Statement) sondern auf das vergewaltigte "do"/"while"
drum herum. Es gibt Programmiersprachen, die Blöcken Labels verpassen
können, da sieht das anders aus weil explizit als solches erkennbar
("OUTER: do ... continue OUTER").

Wenn man sich also schon nicht an das Paradigma "oben rein, unten
raus" für jeden betrachteten Block halten will, dann kann man m.E.
genauso gut ausdrücklich das tun, was man hier erkennbar sowieso am
liebsten machen will, und das Label nebst seinem "goto" direkt
benutzen. Das sieht man wenigstens was los ist.

Mit "return" ist es ähnlich. Bei kleinen Funktionen ist es egal weil
man das "return" ziemlich sofort sieht, aber bei grösseren Funktionen
muss man oft beim "return" irgendwas aufräumen und ein tief
verschachtelt liegendes "return" ist bei nachträglichen Änderungen
leicht zu übersehen. Da ziehe ich es vor, statt dessen ans Ende der
Funktion zu springen und dort aufzuräumen.

Aber das ist nur meine persönliche Meinung. Wat dem een sien Uhl, ist
dem Annern sien Nachtigall!

von Matthias (Gast)


Lesenswert?

Hi

Ich persönlich halte das für schlechten Stil. Ist, auf den ersten
Blick, etwas schwer zu durchschauen. In einem etwas größeren Kontext
wirds dann noch schlimmer. Und ob das schneller bzw. kleiner als

char Error;
char Done;
char Avail;


void test( char val )
{
    switch( val ){
      case 0:    // Done = 1, Avail = 0
        Done = 1;
        Avail = 0;
        break;

      case 1:    // Avail = 0
        Avail = 0;
        break;

      case 2:    // nothing
        break;

      default:    // Error = 1, Done = 1, Avail = 0
        Error = 1;
        Done = 1;
        Avail = 0;
    }
}

bei -O3 ist weiß ich auch nicht. Die Compiler sind heutzutage
eigentlich so clever das relativ gut zu optimieren.

Matthias

von Rufus T. Firefly (Gast)


Lesenswert?

Lästig wird es, wenn solche switch-Statements so lang werden, daß sie
mehrere Bildschirmseiten füllen.

Wenn es sehr viele case-Anweisungen sind, könnte man über eine
Funktionspointertabelle nachdenken, aber das ist von der jeweiligen
Anwendung abhängig. Das hängt halt stark von der zu realisierenden
Zustandsmaschine ab, ob sich eine Tabelle eignet.

Prinzipiell ist so ein Fall bei geeigneter Strukturierung mit den
Möglichkeiten von C++ sehr schön zu handhaben; das allen Fällen gemeine
kommt in eine Basisklasse, der Rest wird überladen.

Da es Peter aber um einen MCS51 geht, würde ich das Thema C++ hier
schon als beendet ansehen ...

von Peter D. (peda)


Lesenswert?

@A.K.

auf die einfachste Sache kommt man nicht.
Das goto wird wohl in diesem Fall das beste sein (hatte ich schon ganz
vergessen, daß es das in C gibt).


@Matthias,

ja, der WINAVR kann sowas zusammen fassen (habs mir im Listing
angesehen).

Aber es geht mir auch nicht um die Codegröße, sondern um die
Fehlersicherheit und Lesbarkeit.

Und gleiche Ausdrücke an mehreren Stellen laden ja gerade dazu ein,
einen zu vergessen, wenn man mal was ändern muß und schon ist das Chaos
perfekt.

Das Beispiel sollte ja nur verdeutlichen, was ich will, die ganze
Funktion ist natürlich wesentlich komplexer.


@Rufus,

mit C++ kenne ich mich nicht aus, wäre aber interessant, wie das dann
aussehen würde. Soweit ich weiß, kann der WINAVR doch auch C++.


Peter

von A.K. (Gast)


Lesenswert?

Um qualifizierte Aussagen über bessere Alternativen machen zu können,
reicht das stark abstrakte Beispiel nicht aus.

Dennoch ein Tip wie man manche Probleme auch angehen kann, ob's hier
nun passt oder nicht: Eine Variante, die mir bei komplexen State
Machines einfällt, ist das was ich jetzt mal Metaprogrammierung nennen
möchte. Wenn die verwendete Sprache sich schlecht zur übersichtlichen
Abbildung eines Problems eignet, man andererseits eine Idee hat wie das
besser aussehen könnte: Man kann C Code auch automatisch generieren. Und
das muss kein riesiger Compiler sein, bisweilen reicht ein relative
simples Scripting aus um aus der eigenen Darstellung den C Code zu
erzeugen. Oder es gibt fertige Tools.

Beispiele aus der Praxis jenseits von µController Anwendungen um das
Prinzip zu illustrieren (und weil ich mich da besser auskenne): Man
kann einen Parser für komplexe Kommandosprachen komplett selber
schreiben. Man kann aber auch Tools verwenden, die den grössten Teil
davon automatisch erledigen, wie "flex" für die Identifikation
syntaktischer Elemente wie Keywords, Namen, Zahlen etc und "bison"
für die Kommandosyntax selbst. Das Resultat ist unverständlicher C
Code,ein Rattenschwanz von Tabellen und wird dadurch nicht unbedingt
kürzer. Aber der selbst zu schreibende Quelltext ist sauber lesbar und
kompakt.

Ich würde mich auch arg wundern, wenn nicht schon jemand so etwas für
die für Embedded Systems typischen State Machines gebaut hat, evtl.
sogar mit grafischer Darstellung und mit Doku als Nebeneffekt. Das dann
freilich wohl nicht für lau sondern für 4stellige Euros.

von tom-muc (Gast)


Lesenswert?

Hi Peter,

laß die Finger vom Goto, wenn Du wartbaren Code haben willst! Sicher,
das ist eine alte Streitfrage unter Programmierern. Ich halt's da
pragmatisch: wenn Du z.B. ein Goto-Label als ZENTRALEN Errorhandler
brauchst, ist das m. E. nach ok. In Deinem Fall werden das aber ein
ganzer Satz von Goto und entsprechenden Labels - die
Unübersichtlichkeit ist vorprogrammiert!

Besser, meiner Erfahrung nach: (insbesondere, wenn Du nicht mit jedem
Byte knabbern mußt und, was hier sowieso unangebracht wäre, wenn es
nicht zeitkritisch z.B. im Interrupt ist) Kapseln in Funktionen und
Verwendung eines switch-Statements, wenn's zu viele Zustände werden,
dann besser über Funktions-Tabellen.

Schönen Tag noch,
Thomas

P.S.: moderne Compiler (auch der GCC) optimieren Deinen Code sowieso
"in Grund und Boden", wenn Du Dir später den Code im Debugger oder
Assemblercode anschaust, wirst Du feststellen, das der Compiler, der
alte Schlaumeier, von ganz allein auf eine Goto-Label-Technologie
gekommen ist! :-)

von Matthias (Gast)


Lesenswert?

Hi

@peter

>Aber es geht mir auch nicht um die Codegröße, sondern um die
>Fehlersicherheit und Lesbarkeit.

Lesbarkeit? Also das was du da oben als Beispiel gennant hast ist IMHO
nicht besonders lesbar da nicht eindeutig erkennbar ist was bei welchem
case: passiert (in den Kommentaren steht es aber jeder weiß wie das ist
mit den Kommentaren...)

>Und gleiche Ausdrücke an mehreren Stellen laden ja gerade dazu ein,
>einen zu vergessen, wenn man mal was ändern muß und schon ist das
>Chaos perfekt.

Dafür gibt es #define, const, sed und Ctrl-{R,H....}.

Matthias

von Jörg Wunsch (Gast)


Lesenswert?

lex + yacc (die Urväter von flex + bison, die ich einfach als moderne
Abkömmlinge ansehen würde) waren meine wesentliche Motivation,
realloc() noch (nach-)zuimplementieren.  Sicher, das ist nichts für
einen ATtiny2313, wohl auch noch nichts für einen ATmega8, aber wenn
man sowieso eine ATmega128 in der Tasche hat für ein Projekt
(vielleicht sogar noch mit externem RAM, den man dem malloc()
spendieren kann), dann kann das durchaus eine brauchbare Alternative
für die automatisch generierten state machines sein.  Die Dinger sind
nicht ganz klein, aber oft Welten schneller als irgendwelche string-
Parsereien ,,zu Fuß''.

Btw., ich weiß gar nicht ganz genau, was die aktuelle Lage bezüglich
der Lizensierung von bison-generiertem Code ist.  Eine zeitlang waren
die GNU-Freaks da mal der Auffassung, dass der generierte Code eine
`derived work' irgendwie im Sinne der GPL sei.  Ggf. gibt's auch
byacc
(Berkeley yacc), der dieses Problem auf keinen Fall hat.  flex hat es
sowieso nicht, da er außerhalb von GNU entstanden ist.  Wer diese
Tools in proprietären Applikationen einsetzt, sollte also vorher
zumindest einen Blick werfen.

Bezüglich des originalen Problems würde ich wohl auch zu goto
tendieren.  Alternativ kann man vielleicht mit inline-Funktionsblöcken
was erreichen.

von A.K. (Gast)


Lesenswert?

> Die Dinger sind nicht ganz klein

Entstanden auf dem Urvater von Unix, der PDP-11. 64KB Code+Daten. Hat
damals ein kompletter Compiler-Pass (von 2) reingepasst. Ich hatte mal
ein 68K-System dessen 1-Pass Compiler komplett in 128KB lief
(wenngleich knapp). Hat jemand Lust, einen C Compiler für AVR auf dem
ATmega128 selbst laufen zu lassen (Ultra-Bootloader sozusagen)? Möglich
wär's.

von peter dannegger (Gast)


Lesenswert?

Vielen Dank, Ihr habt mich vom Goto überzeugt.

Mit Goto sieht es gut und übersichtlich aus und ich mußte nichts
doppelt schreiben.

Mit der Schleife sah es unübersichtlich aus.


Falls es jemand interessiert:

Die Kommandos sind bereits Binärwerte, einen Parser brauche ich daher
nicht.

Das Ganze ist ein Protokollumsetzter I2C<->UART.
Von der UART gehts über 2 10kV-Optokoppler auf den eigentlichen
Steuer-µC.

Außerdem müssen noch digitale Steuersignale in beiden Richtungen
übertragen werden. Es egeben sich also insgesamt 6 Datenrichtungen.
Dabei darf es aber zu keinem Deadlock kommen (gegenseitiges
Blockieren).
Und es muß die Konsistenz der Daten gesichert werden
(Fehlererkennung->Retry).
Nebenbei muß der µC auf der 10kV-Seite überwacht werden, ob er noch am
Leben ist (Alive-Frames) um dann bei Bedarf resettet zu werden.


Peter

von A.K. (Gast)


Lesenswert?

Der Parser war nur Beispiel für Codegeneratoren gedacht. Mir war klar
dass dies nicht direkt auf die Aufgabe passt.

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.