www.mikrocontroller.net

Forum: Compiler & IDEs GCC Codebeispiel, doppelte variablen?


Autor: Martin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo allerseits,

nachdem ich bis jetzt beim Assembler geblieben bin, dacht ich mir, ich
schnupper mal in ein paar C Codes rein und versuche mir das anzueignen.
Leider finde ic immermal einige sachen, die für mich wenig sinn machen
oder wo ich doch eine schnellere und sinnvollere Implementierung in ASM
gewählt hätte.
meine erste frage ziehlt auf dieses Codebeispiel hin.

SIGNAL (SIG_OVERFLOW0)
{
    static unsigned char count_ovl0;
    unsigned char ovl0 = count_ovl0+1;

    if (ovl0 >= 39)
    {
        // mach was

        ovl0 = 0;
    }

    count_ovl0 = ovl0;
}

warum wird hier noch eine Variable ov10 eingeführt? reicht es nicht,
nur mit count_ov10 zu arbeiten? die kann man doch genauso hochzählen.
Wo ist der Vorteil in dieser Implementierung, bzw gibt es einen? Ich
hätte es eher so implementiert:

SIGNAL (SIG_OVERFLOW0)
{
    static unsigned char count_ovl0;
     if (count_ovl0 >= 39)
    {
        // mach was

        count_ovl0= 0;
    }

    count_ovl0 += l;
}

Außerdem hab ich noch folgendes Beispiel aus dem zum C programm
kreierten ASM Code gefunden:
104:        if ((PINC & 0x03))
    IN      R24,0x13         In from I/O location
    CLR     R25              Clear Register
    ANDI    R24,0x03         Logical AND with immediate
    ANDI    R25,0x00         Logical AND with immediate
    OR      R24,R25          Logical OR
    BREQ    PC+0x04          Branch if equal

Hier ist doch das "ANDI    R25,0x00" völlig überflüssig, oder seh ich
da was falsch? Vielleicht kann mir ja jemand weiterhelfen. Vielen Dank
und liebe Grüße

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> ...oder wo ich doch eine schnellere und sinnvollere Implementierung
> in ASM gewählt hätte.

Klar, wirklich sorgfältiger Assembler-Code kann in der Tat schneller
und kleiner (beim AVR oft dasselbe) sein als compilierter.  Die Gründe
für die Benutzung eines Compilers sind ja andere:

. sehr viel weniger Wartungsaufwand (Erstellen, Debuggen, Pflege), ich
  würde wenigstens den Faktor 10 auf Grund eigener Erfahrung ansetzen

. man findet schneller einen Einstieg in eine neue Maschine, ohne
  deren Befehlssatz von vornherein bis ins letzte i-Tüpfelchen
  durchschaut zu haben, und bekommt dann in der Regel bereits besseren
  Maschinencode, als man ihn selbst schreiben würde

. ein vernünftig geschriebenes Programm in einer höheren Sprache hat
  einen deutlich besseren Autodokumentations-Effekt als ein
  Assemblerprogramm; die dadurch bei letzterem für die Wartbarkeit
  notwendige weitere Dokumentation schlägt sich dann wieder auf den
  Kostenaspekt von Punkt 1 nieder (sieh mal ,Kosten' hier ganz
  wertfrei: in deiner Freizeit ist das dann also einfach das Maß an
  aufgewändeter Zeit, auch wenn sie dir so und so keiner bezahlt
  hätte)

> SIGNAL (SIG_OVERFLOW0)

Heißt seit avr-libc 1.4 übrigens "ISR(TIMER0_OVF_vect)", nur
nebenbei.

> warum wird hier noch eine Variable ov10 eingeführt? reicht es nicht,
> nur mit count_ov10 zu arbeiten?

Ja, würde in diesem Falle wohl genügen.  Aber (und hier hast du einen
Unterschied zur Assemblerprogrammierung): der Compiler wird (mal unter
der Voraussetzung eingeschalteter Optimierung, alles andere ist nicht
Sinn der Sache) wohl aus beiden Implementierungen den gleichen Code
erzeugen.  Damit ist dem Programmierer mehr Wahlfreiheit gegeben in
der Darstellung seines Programmes: wenn es dem Verständnis des
Algorithmus dienlicher ist, eine Hilfsvariable zu benutzen, dann kann
und sollte er das tun -- der Compiler ist dann ,,intelligent''
genug,
diese in der tatsächlichen Implementierung wegzulassen oder
anderweitig zusammenzufassen.

Die hier gezeigte Implementierung hätte übrigens sehr viel Sinn für
den Fall, dass count_ovl0 als "volatile" deklariert wäre, weil
Zugriffe auf derartige Variablen vom Compiler nicht optimiert werden.
In diesem Falle würde die Hilfsvariable ovl0 dann als eine Art
,lokaler Cache' funktionieren, um die Nicht-Optimierbarkeit von
count_ovl0 nicht zu einem Nachteil für die ISR werden zu lassen.

> Hier ist doch das "ANDI    R25,0x00" völlig überflüssig, ...

Ja, ist es.  Das ist ein altbekanntes Problem des GCC: dieser Compiler
wurde ursprünglich ausschließlich für 32-bit-CPUs konzipiert.

Der C-Standard schreibt nun vor, dass die Datentypen ganzzahliger
Ausdrücke nach bestimmten Regeln aneinander angepasst werden, wobei
alle Teilausdrücke mindestens wie ein `int' zu bewerten sind, ggf.
mit
einem entsprechend größeren Typ (unsigned int, long, unsigned long,
...) falls notwendig.  Damit werden auch 8-bit-Ganzzahlausdrücke
intern erst einmal auf 16 bits erweitert.  Nun ist der C-Standard
ausdrücklich als sogenannter `as if'-Standard spezifiziert: die
Regeln
darin sind nicht als konkrete Implementierungsvorschrift aufzufassen,
aber eine Implementierung muss gewährleisten, dass das Ergebnis sich
genau so verhält, als ob (`as if') die Regeln exakt so implementiert
worden wären.  Wenn also beide Seiten nur 8-bit-Ausdrücke enthalten
und die Implementierung feststellen kann, dass die oberen 8 bits in
der gesamten Rechnung gemäß den festgesetzten Regeln keine Rolle
spielen, dürfen sie auch einfach weggelassen werden.

Nun kommen wir aber wieder zum vorigen Absatz: auf einer 32-bit-CPU
spielt die erste Konvertierungsstufe aller 8-bit-Ausdrücke auf `int'
keine Rolle.  Auf manchen Maschinen sind gar 32-bit-Rechnungen (in
Registern) schneller als 8-bit-Rechnungen (die dann im Ergebnis erst
Teile des Registers maskieren müssen).  Auf Grund seiner Geschichte
hat der GCC daher in der Vergangenheit eher wenig Optimierungen in
diesem Bereich erfahren, verglichen mit vielen anderen Bereichen (bei
denen er teilweise Optimierungen vornimmt, die du als
Assemblerprogrammierer so nicht auf Anhieb gesehen hättest).

GCC 4.1.x ist nach meinen Erfahrungen in dieser Hinsicht ein wenig
besser geworden als die 3er Familie, mit der du vermutlich noch
arbeitest, aber auch hier muss man zuweilen schon einmal etwas
,,nachhelfen'', indem man sich den generierten Assemblercode ansieht
und ggf. den C-Code hie oder da ein wenig umstellt.  Manchmal hilft
dabei auch eine eingschobene Hilfsvariable, die einen Teilausdruck
aufnimmt und damit dem Compiler deutlich vorgibt, dass an dieser
Stelle wirklich nur 8 bits benötigt werden.

Das Wichtigste an einem Compiler (wirklich das Allerwichtigste) ist
aber, dass er fehlerfreien Code erzeugt, d. h. dass die
Sprachkonstrukte auch tatsächlich so umgesetzt werden, wie vom
Sprachstandard vorgeschrieben.  Nur so kann sich der Programmierer
darauf verlassen, dass er beim Debuggen nicht etwa noch die Fehler des
Compilers suchen muss, sondern seine eigenen.  In dieser Hinsicht muss
der GCC sein Licht nicht unter den Scheffel stellen.  Mir ist derzeit
ein einziger Fall bekannt, bei dem er für den AVR wirklich falschen
Code erzeugt (bei der Übergabe sehr vieler oder sehr großer Parameter
an Funktionen, bspw. 3 x uint64_t).

Autor: Μαtthias W. (matthias) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

deine Implementierung macht was anderes als die
Originalimplementierung.

Zum zweiten Beispiel:
Das hat seine Ursprünge im C-Standard da vor allen Rechenoperationen
alle Variablen mindestens auf int (beim AVR also auf 16 Bit) erweitert
werden. Es gibt Compiler die tun das nicht (im Bereich der 8 Bitter ist
das evtl sinnvoll), der GCC tut das. Du wirst von einem Compiler nie
optimalen Code erhalten. Aber genauso wird er an gewissen Stellen Code
erzeugen der weit besser ist als das was ein Mensch in ASM kodiert.

Matthias

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Martin,

das Codebeispiel ist ein typischer Fall für das volatile-Dilemma:

Variablen, die ein Interrupt setzt und eine andere Funktion abfragt,
müssen als volatile deklariert werden, sonst optimiert der GCC alle
Abfragen außer der ersten gnadenlos weg.

Dann aber wird auch der Interrupthandler aufgebläht, da auch dort die
Variable bei jeder Verwendung ständig neu eingelesen und abgespeichert
wird.

Abhilfe ist dann für die Optimierung eine Zwischenvariable zu nehmen,
die nicht volatile ist. Dies tut das Beispiel.


Eine andere Abhilfe wäre, den Interrupthandler und die Abfragefunktion
in verschiedene Objekte zu packen, wo die Variable einmal nicht
volatile und einmal volatile ist. Damit erhält man den kürzesten Code.

Da volatile nur den Compilevorgang steuert bleibt es für den Linker
trotzdem die selbe Variable.


Peter

Autor: Martin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank für eure Antworten, besonder die ausführliche Antwort von
Dir, Jörg.
Deine Vermutung, war richtig, es handelt sich bei count_ovl0 um eine
volatile definierte Variable. Der sinn eines C Compilers ist mir soweit
schon klar, dewegen möcht ich mich mit dem Thema gern auseinandersetzen.
Kostet zwar erst einmal ein wenig leseaufwand und vor allem ein wenig
übung, aber ich denke, den aufwand holt man beim debuggen und vor allem
beim Widerverwerten von Codeschnipseln schnell wieder raus. Ich war nur
verwundert, das ich so offensichtlich überflüssige befehle auf anhieb
gesehen habe, auch ohne allzu viel erfahrung mit ASM bis jetzt
gesammelt zu haben.

Eine Frage hab ich noch, wo finde ich die Version des installierten
GCC's? Ist die 4.1.x gleichzusetzen mit der Version des AVR Studios?
da habe ich nämlich Version 4.1.2 build 472. 'nen GCC Compiler habe
ich (glaub ich) nicht explizit installiert, ich glaub, der war im AVR
Studio dabei.

Vielen Dank noch einmal und 'nen schönen Abend wünsch ich

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Eine Frage hab ich noch, wo finde ich die Version des installierten
> GCC's?

avr-gcc -v

> Ist die 4.1.x gleichzusetzen mit der Version des AVR Studios?

Nö, AVR Studio hat mit GCC rein gar nichts zu tun, außer dass es
(mittlerweile) eine Schnittstelle bietet, um den AVR-GCC einzubinden.

> 'nen GCC Compiler habe ich (glaub ich) nicht explizit installiert,
> ich glaub, der war im AVR Studio dabei.

Nein, AVR Studio bringt keinen Compiler mit, nur einen (eher
spartanischen) Assembler.  Entweder hast du WinAVR noch mit
installiert, oder du hast wirklich keinen Compiler.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Eine andere Abhilfe wäre, den Interrupthandler und die
> Abfragefunktion in verschiedene Objekte zu packen, wo die Variable
> einmal nicht volatile und einmal volatile ist. Damit erhält man den
> kürzesten Code.

Prima Glatteis.  Kannst dir ja mal die Mühe machen, im Standard
nachzusehen, ob das Ergebnis "undefined" oder "unspecified"
behaviour
ist...  Bis zu "implementation-defined" wird es jedenfalls kaum
reichen.

Erzähl' doch bitte einem C-Anfänger nicht so 'nen Quark.  Die
Variante, die er aufgezeigt hat, ergibt sauberen Code, der vom
Standard ordentlich gedeckt ist.  Sie löst beide Probleme: die
Kommunikation zwischen ISR und Haupt-Kontext genauso wie die
andernfalls fehlende Optimierung innerhalb der ISR, wenn man nur mit
dem volatile qualifizierten Objekt arbeiten würde.  Sie wird das Ganze
letztlich mit minimalem Overhead tun (die temporäre Variable wird am
Ende nur aus ein oder zwei Registern bestehen, nicht etwa aus
separaten RAM-Zellen).

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Jörg,

der Softwerker hier in der Firma fand die Idee aber garnicht so
schlecht.
Auch sieht der erzeugte Assembler einwandfrei aus und Warnings/Errors
gabe auch keine.

Aber wenn Du als GCC-Profi dagegen bist, werd ichs mal sein lassen.


Peter


P.S.:
Ich benutze jetzt wieder die AVR-GCC 3.4.6. Version.
Der 4.1.1. spart oft ein paar wenige Byte aber manchmal verhaut er sich
so richtig und dann sinds viele Byte mehr.
Über die Summe der Projekte war die Bilanz jedenfalls negativ (mehr
Flash).
Und außerdem gehen mir die ständigen Assemblerwarnungen auf den Keks.

Ich kann daher nur raten, für konstant gute Compileergebnisse erstmal
noch beim offiziellen WINAVR mit GCC 3.4.6. zu bleiben.

Autor: Martin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
bei genauerem hinsehen ließ sich auch ein WinAVR auftreiben. Ich werd
mir die aktuelle version mal anschauen, vielen dank noch einmal fürs
geduldige erklären.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Der 4.1.1. spart oft ein paar wenige Byte aber manchmal verhaut er
> sich so richtig und dann sinds viele Byte mehr.

Beispiele?

> Ich kann daher nur raten, für konstant gute Compileergebnisse
> erstmal noch beim offiziellen WINAVR mit GCC 3.4.6. zu bleiben.

Wie lange ,,noch''?  Die nächsten 3 Jahre?  5?  8?

Begreif doch mal: solange du deine Probleme nicht zur Sprache bringst,
mit sauber nachvollziehbaren Beispielen und im richtigen Forum (also
in einem, wo die AVR-GCC-Entwickler wenigstens ansatzweise eine Chance
haben mitzulesen -- avr-gcc-list at nongnu.org), wird sich nichts
ändern, da kannst du noch so lange warten.  ,,Von allein'' wird da
kaum einer was tun.  Eigentlich gibt es derzeit wohl nur einen
einzigen richtig aktiven AVR-GCC-Hacker (Björn Haase).  Wenn ihm
irgendeine Optimierung zu dünn ist, dann repariert er sie (und benutzt
das dann auch in produktivem Code).  Den Support für ATmega256x haben
wir auch ihm zu verdanken.  Den Lösungsansatz hatten zwar zuvor
bereits andere diskutiert, aber letztzlich war er es, der das dann
umgesetzt hat.  Wenn dir aber irgendeine Optimierung zu dünn ist,
dann wird er davon wohl einfach mal gar nichts wissen.  Jemand anders,
der etwas daran tun könnte, ebenfalls nicht.

Das nächste ,,offizielle WinAVR'' wird auf jeden Fall mit GCC 4.1.1
daher kommen (falls nicht zufällig noch ein 4.1.2 zuvor freigegeben
wird, aber das sieht mir nicht so aus).  Eric Weddington hat, soweit
ich das mitbekommen habe, keinen Bock mehr, obsolete GCC-Versionen
noch als Alternative mitzugeben, weil ihm der logistische Aufwand zu
hoch ist, eine entsprechende Auswahl (,,Möchten Sie die Version von
heute, von gestern, von voriger Woche oder von vor 3 Jahren
installieren?'') in den Installer zu bauen.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Jörg,

> Beispiele?

Ich mach da mal nen neuen Thread auf mit dem Beispiel.


> Wie lange ,,noch''?  Die nächsten 3 Jahre?  5?  8?

Ehrlich gesagt, ich hab da überhaupt keine Ahnung, wie lange GCCs so
zum Reifen brauchen.
Ich weiß auch erst, daß ich die 3.4.6 habe, nachdem gesagt wurde, die
sei ja uralt und die 4.1.x sei besser. Davor hat es mich einfach nicht
interessiert.


> wo die AVR-GCC-Entwickler wenigstens ansatzweise eine Chance
haben mitzulesen -- avr-gcc-list at nongnu.org)

O.k. werd ich machen.
Sind ja schon 2 Probleme.
Ich stelle das Beispiel aber erstmal hier rein.


Peter

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.