Forum: Compiler & IDEs avr-gcc - unnützer Assembler-code?


von Rene F. (fook)


Lesenswert?

Habe mit dem neuen AVR-Studio 6 ein kleines Programm geschrieben und mit 
-O0 (Null) compiliert - Zielprozessor ist der ATMEGA2560. Bei der 
Sichtung des lss-Files fielen mir ein paar Ungereimtheiten im erzeugten 
Code auf, die ich mir nicht erklären kann..

Ausgangsprogramm:
1
int main(void)
2
{
3
  unsigned char a=3, b=4, c;
4
  while (1) {
5
    a++, b++;
6
    c=a+b;
7
  }
8
}

Assemblercode "lss-file" (nur von der main-Routine):
1
0000012a <main>:
2
 12a:  cf 93         push  r28
3
 12c:  df 93         push  r29
4
 12e:  00 d0         rcall  .+0        ; 0x130 <main+0x6>
5
 130:  cd b7         in  r28, 0x3d  ; 61
6
 132:  de b7         in  r29, 0x3e  ; 62
7
 134:  83 e0         ldi  r24, 0x03  ; 3
8
 136:  89 83         std  Y+1, r24  ; 0x01
9
 138:  84 e0         ldi  r24, 0x04  ; 4
10
 13a:  8a 83         std  Y+2, r24  ; 0x02
11
 13c:  89 81         ldd  r24, Y+1  ; 0x01
12
 13e:  8f 5f         subi  r24, 0xFF  ; 255
13
 140:  89 83         std  Y+1, r24  ; 0x01
14
 142:  8a 81         ldd  r24, Y+2  ; 0x02
15
 144:  8f 5f         subi  r24, 0xFF  ; 255
16
 146:  8a 83         std  Y+2, r24  ; 0x02
17
 148:  99 81         ldd  r25, Y+1  ; 0x01
18
 14a:  8a 81         ldd  r24, Y+2  ; 0x02
19
 14c:  89 0f         add  r24, r25
20
 14e:  8b 83         std  Y+3, r24  ; 0x03
21
 150:  f5 cf         rjmp  .-22       ; 0x13c <main+0x12>

Könnt ihr mir vielleicht sagen, warum
1. in 12e ein "rcall .+0" steht? das macht ja ansich keinen Sinn, oder?
Es legt lediglich die aktuelle PC-Adresse+1 auf den Stack (=0x130) und 
erhöht diesen, aber warum?
Dazu: verändere ich den Datentyp von "unsigned char" auf "unsigned 
short" so werden 2 von diesen rcall-s eingefügt...

2. die Anweisungen "a++" und "b++" werden durch eine Subtraktion mit 255 
realisiert (Zeilen 13e und 144) - warum? ist ein increment nicht 
schneller/kürzer?

PS: ich habe nur wenig AVR-Erfahrungen, komme von 68000er und PIC und 
daher ist mir vieles dabei noch unklar...

von Oliver (Gast)


Lesenswert?

Rene F. schrieb:
Rene F. schrieb:
> 2. die Anweisungen "a++" und "b++" werden durch eine Subtraktion mit 255
> realisiert (Zeilen 13e und 144) - warum? ist ein increment nicht
> schneller/kürzer?

a) dauern eigentlich alle Befehle auf dem AVR einen Zyklus, und b) hat 
der gar kein add immediate-Befehl ;) Daher der "Trick" mit der 
Subtraktion von -1.

Über die "Kürze" von Code zu sinnieren, der ohne Optimierung kompiliert 
wurde, ist allerdings sowieso ziemlich zweckfrei.

Oliver

von Rene F. (fook)


Lesenswert?

Oliver schrieb:
> Rene F. schrieb:
>> 2. die Anweisungen "a++" und "b++" werden durch eine Subtraktion mit 255
>> realisiert (Zeilen 13e und 144) - warum? ist ein increment nicht
>> schneller/kürzer?
>
> a) dauern eigentlich alle Befehle auf dem AVR einen Zyklus, und b) hat
> der gar kein add immediate-Befehl ;) Daher der "Trick" mit der
> Subtraktion von -1.

aber einen INC-Befehl hat er schon ... das hatte mich verwundert, dass 
nicht das Naheliegendste benutzt wird..
SUB hat ja immernoch den Nebeneffekt des gesetzen Carry-Flags und das 
wird hierbei garnicht "korrigiert" - selbst bei automatisch generiertem 
Code ohne Optimierung sollte sowas doch nicht erzeugt werden, wenn es 
dadurch zu Problemen mit anderen Teilen kommen kann.

von Michael (Gast)


Lesenswert?

Rene F. schrieb:
> aber einen INC-Befehl hat er schon ... das hatte mich verwundert, dass
> nicht das Naheliegendste benutzt wird..
> SUB hat ja immernoch den Nebeneffekt des gesetzen Carry-Flags und das
> wird hierbei garnicht "korrigiert" - selbst bei automatisch generiertem
> Code ohne Optimierung sollte sowas doch nicht erzeugt werden, wenn es
> dadurch zu Problemen mit anderen Teilen kommen kann.

Wieso sollte das Carry-Flag "korrigiert" werden? Das kann überall im 
Programm verändert werden...

Code, der das Carry-Flag auswertet, muss es natürlich zuvor richtig 
setzen.

Und in Interrupts wird das SREG, und damit das Carry-Flag, gesichert und 
dann wiederhergestellt.

von Rene F. (fook)


Lesenswert?

Michael schrieb:
> Rene F. schrieb:
>> SUB hat ja immernoch den Nebeneffekt des gesetzen Carry-Flags und das
>> wird hierbei garnicht "korrigiert" - selbst bei automatisch generiertem
>> Code ohne Optimierung sollte sowas doch nicht erzeugt werden, wenn es
>> dadurch zu Problemen mit anderen Teilen kommen kann.
>
> Wieso sollte das Carry-Flag "korrigiert" werden? Das kann überall im
> Programm verändert werden...

stimmt schon, aber ich erwarte ja als Programmierer nicht, dass das 
Carry-Flag auf 1 geht nur weil ich eine Zahl inkrementiere, oder?
Das macht debuggen dann schwieriger, wenn sich solch ein Bit verändert, 
ohne dass die arithmetische Operation (a++) das erfordern würde.

nur in dieser Hinsicht ist das seltsam.

von Oliver (Gast)


Lesenswert?

Rene F. schrieb:
> stimmt schon, aber ich erwarte ja als Programmierer nicht, dass das
> Carry-Flag auf 1 geht nur weil ich eine Zahl inkrementiere, oder?

Was du als Programmierer erwarten kannst, ist ja eigentlich nur, daß das 
Assembler-Programm das tut, was dein C-Programm aus Sicht des 
C-Standards tun sollte.

Daher ist es völlig nebensächlich, was der Compiler genau aus dem C-Code 
macht, solange das Programm das tut, was du in den C-Code schreibst.

Und ob unsigned char a++ bei einem Überlauf das Carry-Flag setzt, oder 
auch nicht, ist dem C-Standard herzlich egal. Der kennt gar kein 
Carry-flag.

Oliver

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rene F. schrieb:
> und mit
> -O0 (Null) compiliert

...und dann auch noch erwartet, dass er trotzdem optimal ist.

Naja.  Du hast noch einen Versuch, würde ich mal sagen. ;-)

von Rene F. (fook)


Lesenswert?

Jörg Wunsch schrieb:
> Rene F. schrieb:
>> und mit
>> -O0 (Null) compiliert
>
> ...und dann auch noch erwartet, dass er trotzdem optimal ist.

ihr habt ja Recht - dass es optimal sein würde, ist dann wirklich nicht 
zu erwarten.
Aber ich würd gern den Compiler in dieser Hinsicht verstehen, warum 
manches so oder so übersetzt wird - den "rcall" kann ich mir einfach 
nicht erklären, den Sub vielleicht schon, wenn es denn eine der ersten 
Übersetzungen ist, die im Compiler verdrahtet sind.

Hintergrund des ganzen ist, dass ich gern ein paar Studenten erklären 
möchte, wie aus ihren C Programmen Maschinen-Befehle für den Prozessor 
erzeugt werden ohne dass sie selbst Assembler programmieren müssen - und 
da ist leider die Übersetzung ungeeignet. Bei einer Optimierung werden 
ja viele Schritte zwischendrin schon ersetzt und hier im Beispiel wird 
die main-funktion gleich ganz rausoptimiert...

von Stefan E. (sternst)


Lesenswert?

Rene F. schrieb:
> den "rcall" kann ich mir einfach
> nicht erklären

Damit wird auf dem Stack Platz (2 Bytes) geschaffen für lokale 
Variablen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rene F. schrieb:

> Aber ich würd gern den Compiler in dieser Hinsicht verstehen, warum
> manches so oder so übersetzt wird - den "rcall" kann ich mir einfach
> nicht erklären,

Wurde dir ja nun schon erklärt.

> den Sub vielleicht schon, wenn es denn eine der ersten
> Übersetzungen ist, die im Compiler verdrahtet sind.

Der AVR hat keinen Befehl "ADDI", aber ein "SUBI".  "ADDI" hat man
rausgeschmissen (zu Gunsten anderer Befehle), nachdem die IAR-
Entwickler im Entwurf des Ur-AVR angemerkt haben, dass dieser
Befehl überflüssig ist, da er stets durch ein SUBI ersetzbar ist.
(Kann man in einem uralten Dokument bei Atmel nachlesen.)

Damit wird eine Addition also erstmal generisch als Subtraktion
umgesetzt.  Die Erkennung, dass die Addition ja nur mit der Zahl
1 erfolgt war und folglich ein INC genügt, ist dann Aufgabe des
Optimierers.

> Hintergrund des ganzen ist, dass ich gern ein paar Studenten erklären
> möchte, wie aus ihren C Programmen Maschinen-Befehle für den Prozessor
> erzeugt werden ohne dass sie selbst Assembler programmieren müssen - und
> da ist leider die Übersetzung ungeeignet.

Dann solltest du etwas tiefer in den GCC einsteigen und einiges
des Zwischencodes mit heranziehen.  Sieh dir mal das Ergebnis von
-fdump-rtl-all an.

> Bei einer Optimierung werden
> ja viele Schritte zwischendrin schon ersetzt und hier im Beispiel wird
> die main-funktion gleich ganz rausoptimiert...

Man kann an diversen Stellen die Optimierung verhindern mit paar
Tricks (attribute "used", volatile etc.).

Du solltest auf jeden Fall ein Beispiel finden, an dem man einerseits
die formal langweilige Codegenerierung ohne Optimierung und
andererseits die tiefgreifenden Änderungen, die die Optimierung dann
mit sich bringt, demonstrieren kann.  Das gibt einem dann ein
Gefühl, dass ein Compiler nicht nur eine stur blöde Maschine ist
(obwohl er es trotzdem ist ;-), sondern dass darin viele clevere Leute
ihr Wissen eingebracht haben und im Ergebnis ein Maschinencode
entsteht, den ein Programmieranfänger mit reinem Assembler oft nicht
so kompakt hinbekommen hätte.

Nimm bitte die neueste GCC-Version dafür, Johann hat da noch einiges
an Mikro-Optimierungen im AVR-Bereich nachgezogen in letzter Zeit.

von Thomas E. (thomase)


Lesenswert?

Rene F. schrieb:
> int main(void)
> {
>   unsigned char a=3, b=4, c;
>   while (1) {
>     a++, b++;
>     c=a+b;
>   }
> }
Und schreib' nicht sowas sinnloses. Das macht nach aussen hin gar nichts 
und wird deshalb natürlich wegoptimiert. Und Strom verbraucht der 
Controller auch so.
Deklarierst du die Variablen aber global und volatile macht der Code 
zwar immer noch keinen Sinn, aber es wird nicht komplett wegoptimiert.
1
  while (1) {
2
    a++, b++;
3
  a6:  80 91 00 01   lds  r24, 0x0100
4
  aa:  8f 5f         subi  r24, 0xFF  ; 255
5
  ac:  80 93 00 01   sts  0x0100, r24
6
  b0:  80 91 01 01   lds  r24, 0x0101
7
  b4:  8f 5f         subi  r24, 0xFF  ; 255
8
  b6:  80 93 01 01   sts  0x0101, r24
9
    c=a+b;
10
  ba:  90 91 00 01   lds  r25, 0x0100
11
  be:  80 91 01 01   lds  r24, 0x0101
12
  c2:  89 0f         add  r24, r25
13
  c4:  80 93 02 01   sts  0x0102, r24
14
  c8:  ee cf         rjmp  .-36       ; 0xa6 <main>

Das sieht doch jetzt nachvollziehbar aus.

mfg.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rene F. schrieb:
> a) dauern eigentlich alle Befehle auf dem AVR einen Zyklus

Nein.  Beispile: CPSE, RJMP, CALL, RET, RETI, MUL, LDS, ADIW ...

> b) hat der gar kein add immediate-Befehl ;)

Doch, allerdings nur für die W-Register, da geht dann ADIW mit 
konstanten
0...63. 1...64 wär sinniger, ist aber nun mal so.

> Oliver schrieb:
>> Rene F. schrieb:
>>> 2. die Anweisungen "a++" und "b++" werden durch eine Subtraktion mit 255
>>> realisiert (Zeilen 13e und 144) - warum? ist ein increment nicht
>>> schneller/kürzer?
>>
>> b) hat der gar kein add immediate-Befehl ;)
>> Daher der "Trick" mit der Subtraktion von -1.
>
> aber einen INC-Befehl hat er schon ... das hatte mich verwundert, dass
> nicht das Naheliegendste benutzt wird.

Das naheliegendste is SUBI/SBIC, denn damit können alle Werte addiert 
und subtrahiert und addiert werden, und die Flags werden auch richtig 
gesetzt.

INC/DEC werden auf den unteren Registern verwendet.

Jörg Wunsch schrieb:

> Dann solltest du etwas tiefer in den GCC einsteigen und einiges
> des Zwischencodes mit heranziehen.  Sieh dir mal das Ergebnis von
> -fdump-rtl-all an.

Und natürlich -fdump-tree-all nicht vergessen und -dp -fverbose-asm 
-save-temps.

Im erzeigten Assembler-Code ist das Resultat besser zu erfassen als in 
einem Disassembly.

Hier mal ein kleines Modul, wo nicht alles wegoptimiert werden kann:
 
1
int f1 (int*);
2
3
int f2 (int i)
4
{
5
    char c = f1 (&i);
6
    return i + c;
7
}
8
9
void f4 (long long, long);
10
11
12
void f3 (long long a, long b)
13
{
14
    f4 (a, b + 0xff0100);
15
}
 
Und hier das Ergebnis mit -Os für einen ATmega8:
1
f2:
2
  push r28   ;  33  pushqi1/1  [length = 1]
3
  push r29   ;  34  pushqi1/1  [length = 1]
4
   ; SP -= 2   ;  38  *addhi3_sp  [length = 1]
5
  rcall .
6
  in r28,__SP_L__   ;  39  *movhi/8  [length = 2]
7
  in r29,__SP_H__
8
/* prologue: function */
9
/* frame size = 2 */
10
/* stack size = 4 */
11
.L__stack_usage = 4
12
  std Y+2,r25   ;  2  *movhi/4  [length = 2]
13
  std Y+1,r24
14
  movw r24,r28   ;  32  *movhi/1  [length = 1]
15
  adiw r24,1   ;  6  *addhi3/3  [length = 1]
16
  rcall f1   ;  7  call_value_insn/2  [length = 1]
17
  ldd r18,Y+1   ;  11  *movhi/3  [length = 2]
18
  ldd r19,Y+2
19
  add r18,r24   ;  12  *addhi3.sign_extend1  [length = 5]
20
  adc r19,__zero_reg__
21
  sbrc r24,7
22
  dec r19
23
  movw r24,r18   ;  49  *movhi/1  [length = 1]
24
/* epilogue start */
25
   ; SP += 2   ;  44  *addhi3_sp  [length = 2]
26
  pop __tmp_reg__
27
  pop __tmp_reg__
28
  pop r29   ;  45  popqi  [length = 1]
29
  pop r28   ;  46  popqi  [length = 1]
30
  ret   ;  47  return_from_epilogue  [length = 1]
31
32
f3:
33
  push r14   ;  37  pushqi1/1  [length = 1]
34
  push r15   ;  38  pushqi1/1  [length = 1]
35
  push r16   ;  39  pushqi1/1  [length = 1]
36
  push r17   ;  40  pushqi1/1  [length = 1]
37
/* prologue: function */
38
/* frame size = 0 */
39
/* stack size = 4 */
40
.L__stack_usage = 4
41
  ldi r30,-1   ;  15  addsi3/3  [length = 4]
42
  sub r15,r30
43
  sbc r16,__zero_reg__
44
  sbci r17,-1
45
  rcall f4   ;  25  call_insn/2  [length = 1]
46
/* epilogue start */
47
  pop r17   ;  43  popqi  [length = 1]
48
  pop r16   ;  44  popqi  [length = 1]
49
  pop r15   ;  45  popqi  [length = 1]
50
  pop r14   ;  46  popqi  [length = 1]
51
  ret   ;  47  return_from_epilogue  [length = 1]
52
53
  .ident  "GCC: (GNU) 4.8.0 20120919 (experimental)"
 
Wie man sieht, stehen da sogar Kommentare wozu das "rcall ." gut ist.

>> Bei einer Optimierung werden
>> ja viele Schritte zwischendrin schon ersetzt und hier im Beispiel wird
>> die main-funktion gleich ganz rausoptimiert...
>
> Man kann an diversen Stellen die Optimierung verhindern mit paar
> Tricks (attribute "used", volatile etc.).
>
> Du solltest auf jeden Fall ein Beispiel finden, an dem man einerseits
> die formal langweilige Codegenerierung ohne Optimierung und
> andererseits die tiefgreifenden Änderungen, die die Optimierung dann
> mit sich bringt, demonstrieren kann.

ACK.

C hat noch viele andere Möglichkeiten als den Holzhammer [tm] volatile 
wenn man eine Funktion haben will, die Seiteneffekte hat, damit darin 
nicht alles bis zur Trivialität weggestrichen wird.

> Nimm bitte die neueste GCC-Version dafür, Johann hat da noch einiges
> an Mikro-Optimierungen im AVR-Bereich nachgezogen in letzter Zeit.

Sowie ich das verstehe, geht es darum, dem Compiler bei der Arbeit 
zuzuschauen und eine Vorstellung davon zu bekommen, was er so treibt.

Der ausgegebene Assembler-Code ist nicht immer gut verständlich, das war 
in den älteren Versionen teilweise besser.  4.6 gibt für f3 von oben 
etwa folgendes aus:
1
f3:
2
  push r10   ;  39  *pushqi/1  [length = 1]
3
  push r11   ;  40  *pushqi/1  [length = 1]
4
  push r12   ;  41  *pushqi/1  [length = 1]
5
  push r13   ;  42  *pushqi/1  [length = 1]
6
  push r14   ;  43  *pushqi/1  [length = 1]
7
  push r15   ;  44  *pushqi/1  [length = 1]
8
  push r16   ;  45  *pushqi/1  [length = 1]
9
  push r17   ;  46  *pushqi/1  [length = 1]
10
/* prologue: function */
11
/* frame size = 0 */
12
/* stack size = 8 */
13
.L__stack_usage = 8
14
  mov r10,__zero_reg__   ;  59  *reload_insi  [length = 6]
15
  ldi r30,hi8(16711936)
16
  mov r11,r30
17
  ldi r30,hlo8(16711936)
18
  mov r12,r30
19
  mov r13,__zero_reg__
20
  add r14,r10   ;  16  addsi3/1  [length = 4]
21
  adc r15,r11
22
  adc r16,r12
23
  adc r17,r13
24
  rcall f4   ;  26  call_insn/3  [length = 1]
25
/* epilogue start */
26
  pop r17   ;  49  popqi  [length = 1]
27
  pop r16   ;  50  popqi  [length = 1]
28
  pop r15   ;  51  popqi  [length = 1]
29
  pop r14   ;  52  popqi  [length = 1]
30
  pop r13   ;  53  popqi  [length = 1]
31
  pop r12   ;  54  popqi  [length = 1]
32
  pop r11   ;  55  popqi  [length = 1]
33
  pop r10   ;  56  popqi  [length = 1]
34
  ret   ;  57  return_from_epilogue  [length = 1]
Da hat man wenigstens noch den Summanden in Klartext stehen, wenn auch 
dezimal: 16711936.

von Karl H. (kbuchegg)


Lesenswert?

Rene F. schrieb:

> aber einen INC-Befehl hat er schon ... das hatte mich verwundert, dass
> nicht das Naheliegendste benutzt wird..

Ob Naheliegend oder nicht ist etwas, das im Auge des Betrachters liegt.

Hier, in diesem konkreten Beispiel ist es völlig Wurscht ob SUBI oder 
INC. Beide Befehle erfüllen hier den gleichen Zweck.

> SUB hat ja immernoch den Nebeneffekt des gesetzen Carry-Flags und das
> wird hierbei garnicht "korrigiert"

Und?
Im konkreten Beispiel stört das ja nicht.

von Rene F. (fook)


Lesenswert?

@Jörg und @Johann... vielen dank für die Hinweise! ich gehe schon auf 
mehr als nur den Assembler-code ein und werde mal sehen, was ich aus dem 
gcc noch an Informationen zum Verständnis bekommen kann.

@Thomas... dass der Code unsinnig ist, weiss ich auch - es sollten ja 
eben nur ein paar einfache Befehle sein und man daran die Umsetzung in 
Assembler sehen und ich dachte eben, dass arithmetische Operationen fast 
1:1 übersetzt werden.

@KarlHeinz... stimmt schon, dass es nicht stört, wenn das Carry gesetzt 
wird oder nicht. mein Verständnis vom ersten Compilerschritt war, dass 
jede Anweisung zunächst in ein Assemblerkonstrukt transformiert wird 
(ja, zwischendrin mit Syntaxbaum,Abhängigkeiten, etc.). Und das würde ja 
heissen, dass erstmal jeder Inkrement zu einem SUBI wird - egal was 
rundrum steht.

Nun hat der atmel ja verschiedene SUB-Befehle (mit und ohne Carry) und 
deshalb ist es in dem Beispiel auch egal, was damit passiert - bei den 
PICs gibt es eben nur einen SUB-Befehl und der ist immer mit Carry, 
weshalb es mich so verwundert hatte...
wie gesagt: meine Atmel-Erfahrungen stehen noch am Anfang und ich bin 
für eure Hilfe wirklich dankbar.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rene F. schrieb:
> mein Verständnis vom ersten Compilerschritt war, dass
> jede Anweisung zunächst in ein Assemblerkonstrukt transformiert wird

Nein.  Im Gegenteil, dieser Schritt erfolgt sogar erst recht spät.
Intern wird sehr lange noch mit einer abstrakten Beschreibung
gearbeitet.  Auf diese Weise kann man einen Großteil der Optimierungen
bereits unabhängig von der Zielmaschine vornehmen.

(Johann möge mich korrigieren, wenn ich hier schief liege, er steckt
da tiefer drin.)

von Karl H. (kbuchegg)


Lesenswert?

Rene F. schrieb:
> @Jörg und @Johann... vielen dank für die Hinweise! ich gehe schon auf
> mehr als nur den Assembler-code ein und werde mal sehen, was ich aus dem
> gcc noch an Informationen zum Verständnis bekommen kann.

Das Problem ist meines Erachtens, dass reale Compiler zu kompliziert 
sind und zu viele Nebenbedingungen beachten müssen, um damit jemanden 
der da nicht so involviert ist, die grundsätzliche Arbeitsweise eines 
Compilers nahe zu bringen.

Mir hat es am Anfang (in der Vorlesung Compilerbau) sehr geholfen, dass 
wir erst mal einen Compiler für eine fiktive Maschine gebaut haben. 
Konkret war das eine Stack-Maschine so dass der ganze Themenkreis 
"Registerallokierung" erst mal weggefallen ist. Mit so einer 
Stackmaschine ist es dann auch nicht so schwer, zb den Themenkreis 
"Expression Parsing" nachzuvollziehen und in eigenen Programmen mal 
damit zu experimentieren.

> @Thomas... dass der Code unsinnig ist, weiss ich auch - es sollten ja
> eben nur ein paar einfache Befehle sein und man daran die Umsetzung in
> Assembler sehen und ich dachte eben, dass arithmetische Operationen fast
> 1:1 übersetzt werden.

Ist ja grundsätzlich nicht von der Hand zu weisen. Nur solltest du dich 
nicht auf C-typische Sonderfälle wie die Inkrement Operatoren 
versteifen, bzw. die die pragmatische Sichtweise aneignen, dass
  i++;
auch nur eine andere Schreibweise für
  i = i + 1;
ist, die es dem Compiler ermöglichen kann, anderen Code zu generieren 
und zwar ohne, dass man im Compiler riesigen Aufwand treiben muss. Die 
Inkrement Operatoren sind also mehr 'Hinweise' an den Compiler, damit 
der ohne große Datenflussanalyse zum gleichen Ergebnis kommt.

Welche CPU Instruktionen dann tatsächlich benutzt werden, ist damit 
überhaupt nicht ausgesagt. Ein i++ kann in einem INC münden, muss es 
aber nicht. Wenn die Kosten für einen INC und einen ADI (oder so wie 
hier SUBI) identisch sind, kann es für den Compilerbauer einfacher sein, 
die Fälle

   i++;
   i = i + 1;
   i = i + 5;

über einen gemeinsamen Kamm zu scheren. Es ändert sich nur die 
Additionskonstante. In 2 Fällen ist sie 1 und in einem Fall ist sie 5. 
Aber vom Prinzip her ist es immer die gleiche Operation.

In Baumsicht ist das

             Assign
             /   \
            /     \
     Destination  Add
                  / \
                 /   \
             Source  Constant


Sind 'Source' und 'Destination' gleich, dann kann dieser Ausdruck unter 
Umständen zu einem 'Inkrement' vereinfacht werden. Kann - muss aber 
nicht. Denn dieser Teilbaum ist ja wieder in einen größeren Kontext 
eingebunden.


> @KarlHeinz... stimmt schon, dass es nicht stört, wenn das Carry gesetzt
> wird oder nicht. mein Verständnis vom ersten Compilerschritt war, dass
> jede Anweisung zunächst in ein Assemblerkonstrukt transformiert wird
> (ja, zwischendrin mit Syntaxbaum,Abhängigkeiten, etc.).

Du unterschätzt die Zwischenschritte. Die haben viel mehr Bedeutung als 
dann hinten nach die eigentliche Assemblergenerierung. Die ist mehr als 
Postprozess zu sehen, die von einer abstrakten Zwischenschicht auf die 
tatsächlichen Befehle abbildet. Die eigentliche Compilerarbeit findet 
auf diesen Zwischenschichten statt! Die Assemblergenerierung ist mehr 
als Postprozess zu sehen. D.h. dieser Teil weiß unter Umständen gar 
nicht mehr, dass da ursprünglich mal ein ++ stand.

> Nun hat der atmel ja verschiedene SUB-Befehle (mit und ohne Carry) und
> deshalb ist es in dem Beispiel auch egal, was damit passiert

Nicht ganz.
Ohne jetzt für alle Prozessoren verallgemeinern zu wollen:
Arithmetische Operationen berücksichtigen das Carry Flag oder sie 
berücksichtigen es nicht - in der Arbeitsweise der Operation. Aber die 
Flags werden üblicherweise von der Operation immer beeinflusst. D.h. 
egal ob ein SUB oder ein SBC benutzt wird, das Carry Flag hat am Ende 
der Operation immer eine gewissen Bedeutung und ist von der Operation 
beeinflusst worden (genauso wie das Zero Flag, das Half Carry Flag und 
was da sonst noch so kreucht und fleucht)

von Dennis H. (t1w2i3s4t5e6r)


Lesenswert?

Rene F. schrieb:
> 2. die Anweisungen "a++" und "b++" werden durch eine Subtraktion mit 255
> realisiert (Zeilen 13e und 144) - warum? ist ein increment nicht
> schneller/kürzer?

Und was ist, wenn a und b kein char sondern uint16_t sind? dann geht 
auch wieder inc nich...


MfG Dennis

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

zieht man einfach 65535 ab

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.