Forum: Compiler & IDEs etwas akademische Frage zu C


von Micha (Gast)


Lesenswert?

Die folgende Frage ist zugegebenermaßen ein wenig akademisch, praktisch 
läßt sich das Problem sehr leicht umgehen, indem man ordentlich 
programmiert, also Zuweisung für Zuweisung einzeln aufschreibt.

In C sind ja solche Zuweisungen erlaubt:

a = b = c = bla;

Gestern aben hab ich kurz vorm Sandmännchen sowas probiert:

a = b += c = bla;

Nicht in einem simplen Testprogramm, sondern inmitten eines komplexeren 
Programms. Wie fast zu erwarten, kam nicht das erwartete Ergebnis raus:
Ewartet hätte ich, daß a und c zugewiesen werden, und in b aufsummiert 
wird.
Leider hab ich momentan keinen Zugang zu einer C Umgebung (bin auf 
Arbeit), sonst könnte ich das Verhalten mittels Ausprobieren verstehen. 
Muß ich bis heute abend warten. Aber da ich es vor Neugier nicht 
aushalte die Frage: was passiert bei so einer Zuweisung? Der Compiler 
hat zumindest nicht gemeckert.
Meine Vermutung: die Zuweisung wird von rechts nach links abgearbeitet? 
Hatte gedacht jede Variable auf der linken Seite hat ihren eigenen 
Zuweisungsoperator direkt zur rechten Seite, aber das scheint nicht so 
zu sein?

Und ja, ich weiß: auf diese Art programmieren ist ohnehin kein guter 
Stil.

von Peter II (Gast)


Lesenswert?

Micha schrieb:
> a = b += c = bla;

jeder Operator hat eine Priorität, damit (müsste) sich das so ergeben

a = ( b += (c = bla) )

daraus ergibt sich

a = ( b += bla )

daraus

a = ( b += bla )


(ich hoffe das stimmt so, es wird aber sonst sich jemand finden der den 
fehler findet)

von Teo D. (teoderix)


Lesenswert?


von Yalu X. (yalu) (Moderator)


Lesenswert?

Peter II schrieb:
> a = ( b += bla )
>
> daraus
>
> a = ( b += bla )
>
> (ich hoffe das stimmt so, es wird aber sonst sich jemand finden der den
> fehler findet)

Nur ein Copy-Paste-Fehler. In der letzten Zeile (die zwar richtig ist)
wolltest du wahrscheinlich schreiben:

a = ( b + bla )

Insgesamt kann man die Anweisung:

1
a = b += c = bla;

folgendermaßen auseinanderpfriemeln:

1
c = bla;
2
b += c;   // d.h. b = bla + c;
3
a = b;    // d.h. a = bla + c;

Da die Zuweisungsoperatoren rechtsassoziativ sind, werden die einzelnen
Zuweisungen also einfach der Reihe nach, beginnend bei der rechten,
hingeschrieben.

von Dr. Sommer (Gast)


Lesenswert?

Micha schrieb:
> Leider hab ich momentan keinen Zugang zu einer C Umgebung (bin auf
> Arbeit), sonst könnte ich das Verhalten mittels Ausprobieren verstehen.

ideone.com hilft ;-)

von Micha (Gast)


Lesenswert?

Danke für die Antworten auf die praxisbezogen zugegebenermaßen nicht so 
wichtige Frage.

Das mit ideone.com find ich toll! Genau so eine Ausprobier-Plattform für 
C, zum Testen von Code-Schnipseln, hab ich mir schon immer gewünscht!

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


Lesenswert?

Micha schrieb:
> Danke für die Antworten auf die praxisbezogen zugegebenermaßen nicht so
> wichtige Frage.

Nun, ganz praxisbezogen ist das durchaus nicht ganz unwichtig zu
verstehen, was da in welcher Reihenfolge passiert.  Nicht, dass man
im realen Leben ein "+=" in die Mitte reinschreiben würde, aber
sowas auf einem AVR:
1
DDRA = DDRB = DDRC = 0xFF;

ist zumindest schon mal nicht wirklich optimal.  Der Grund: die
Verkettung (wie oben erläutert) bewirkt, dass DDRB ja nicht direkt
aus 0xFF zugewiesen wird, sondern aus DDRC (und für DDRA dann nochmal
das gleiche).  Da die DDRx als Register natürlich "volatile" markiert
sind, zwingt die Verkettung den Compiler, wirklich DDRC erst zurück
zu lesen, bevor der Wert an DDRB zugewiesen wird.

Worst case, man nehme einen ATmega1281 o. ä.:
1
DDRA = DDRB = DDRC = DDRD = DDRE = DDRF = DDRG = 0xFF;

Hausaufgabe dazu: welcher Wert steht am Ende in DDRA? ;-)

von Peter D. (peda)


Lesenswert?

1
  x = UDR0 = 'a';
Was steht nun in x?

von City Cobra (Gast)


Lesenswert?

Peter D. schrieb:
> Was steht nun in x?

nicht definiert oder "0" ?

von Falk B. (falk)


Lesenswert?

@Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite


>DDRA = DDRB = DDRC = DDRD = DDRE = DDRF = DDRG = 0xFF;

>Hausaufgabe dazu: welcher Wert steht am Ende in DDRA? ;-)

Ich rate mal. Da wahrscheinlich einer der hohen Ports nicht alle Bits 
implementiert hat, werden beim Rücklesen irgendwo Nullen reinkommen und 
DDRA != 0xFF sein.

Aber wer sowas macht, frißt auch kleine Kinder!

von Falk B. (falk)


Lesenswert?

@  Peter Dannegger (peda)

>  x = UDR0 = 'a';

>Was steht nun in x?

Das letzte empfangene Byte vom UART, nicht 'a'.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Falk B. schrieb:
> Das letzte empfangene Byte vom UART, nicht 'a'.

Man könnte ja RX mit TX verbinden, dann doch 'a' ;-)

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


Lesenswert?

Falk B. schrieb:
> Da wahrscheinlich einer der hohen Ports nicht alle Bits implementiert
> hat, werden beim Rücklesen irgendwo Nullen reinkommen und DDRA != 0xFF
> sein.

Yep, 0x3F oder sowas. :)

von Micha (Gast)


Lesenswert?

Hilfe! Ich will hier raus!!!

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jörg W. schrieb:
> Worst case, man nehme einen ATmega1281 o. ä.:
> DDRA = DDRB = DDRC = DDRD = DDRE = DDRF = DDRG = 0xFF;
>
> Hausaufgabe dazu: welcher Wert steht am Ende in DDRA? ;-)

Beim GCC: 0xFF

Peter D. schrieb:
> x = UDR0 = 'a';
> Was steht nun in x?

Beim GCC: 'a'

Man sollte sich aber nicht darauf verlassen, denn:

1
The implementation is permitted to read the object to determine the
2
value but is not required to, even when the object has volatile-
3
qualified type.

Also besser die Zuweisungen einzeln hinschreiben.

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


Lesenswert?

Yalu X. schrieb:
>> Worst case, man nehme einen ATmega1281 o. ä.:
>> DDRA = DDRB = DDRC = DDRD = DDRE = DDRF = DDRG = 0xFF;
>> Hausaufgabe dazu: welcher Wert steht am Ende in DDRA? ;-)
>
> Beim GCC: 0xFF

Nö, es war wirklich 0x3F.

von Hungerkünstler (Gast)


Lesenswert?

Falk B. schrieb:
> Aber wer sowas macht, frißt auch kleine Kinder!

Ich mag Kinder .....  aber ich schaff' kein ganzes.

von Falk B. (falk)


Lesenswert?

@  Hungerkünstler (Gast)

>> Aber wer sowas macht, frißt auch kleine Kinder!

>Ich mag Kinder .....  aber ich schaff' kein ganzes.

Versuch's mit nem Kinderdöner! ;-)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jörg W. schrieb:
> Nö, es war wirklich 0x3F.

Welche Version?

Aus dem aktuellen GCC-Manual:

1
However when assigning to a scalar volatile, the volatile object is not
2
reread, regardless of whether the assignment expression's rvalue is used
3
or not.

Folgender C-Code

1
  DDRA = DDRB = DDRC = DDRD = DDRE = DDRF = DDRG = 0xFF;
2
  x = UDR0 = 'a';

ergibt mit GCC 5.2.0:

1
  ldi r24,lo8(-1)
2
  out 0x13,r24
3
  out 0x10,r24
4
  out 0xd,r24
5
  out 0xa,r24
6
  out 0x7,r24
7
  out 0x4,r24
8
  out 0x1,r24
9
10
  ldi r24,lo8(97)
11
  sts 198,r24
12
  sts x,r24

Aber wie gesagt: Man sollte sich nicht darauf verlassen.

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


Lesenswert?

Yalu X. schrieb:
> Jörg W. schrieb:
> Nö, es war wirklich 0x3F.
>
> Welche Version?

Irgendeine ältere.

> Aus dem aktuellen GCC-Manual:However when assigning to a scalar
> volatile, the volatile object is not
> reread, regardless of whether the assignment expression's rvalue is used
> or not.

Danke für die Ergänzung.  Mir war das gar nicht bewusst, dass das
"volatile" hier optional für den Compiler ist.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jörg W. schrieb:
> Danke für die Ergänzung.  Mir war das gar nicht bewusst, dass das
> "volatile" hier optional für den Compiler ist.

Die oben zitierte Fußnote, die das klar stellt, taucht zum ersten Mal im
Draft N1516 vom 4.10.2010, also relativ spät auf. Vermutlich wurde das
Verhalten des GCC erst daraufhin geändert.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Yalu X. schrieb:
> Vermutlich wurde das
> Verhalten des GCC erst daraufhin geändert.

Ja, vermutlich. Daher ist für mich eine goldene Regel: "Programmiere 
defensiv!". Das heisst: Im Zweifel Ausdrücke vereinfachen und lieber 
eine Klammer zuviel als zuwenig. Damit kann man schon viele Fallen 
umgehen.

von W.S. (Gast)


Lesenswert?

Jörg W. schrieb:
> Mir war das gar nicht bewusst, dass das
> "volatile" hier optional für den Compiler ist.

Ist aber hier ausnahmsweise mal logisch. Wenn man vorhat, ein flüchtiges 
oder unflüchtiges Register mit irgendwas zu BESCHREIBEN, dann ist es 
völlig wurscht, was zuvor drinstand. Asterix..Obelix..HauDraufWieNix.

W.S.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> Ist aber hier ausnahmsweise mal logisch. Wenn man vorhat, ein flüchtiges
> oder unflüchtiges Register mit irgendwas zu BESCHREIBEN, dann ist es
> völlig wurscht, was zuvor drinstand.

Diese Argumentation verstehe ich nicht ganz. Es kam doch vor der oben 
zitierten Fußnote doch eher drauf an, was nachher drinstand und nicht 
vorher?!?

Also:

   PORTA = PORTB = PORTC = 0xFF;

wurde vor der Draft-Änderung interpretiert als:

   PORTC = 0xFF;
   PORTB = PORTC;
   PORTA = PORTB;

Also: Was jeweils genommen wurde, war der Wert nach der vorhergehenden 
Zuweisung.

Nach der Änderung von gcc funktioniert das nun so:

   PORTC = 0xFF;
   PORTB = 0xFF;
   PORTA = 0xFF;

... jedenfalls habe ich es so verstanden.

: Bearbeitet durch Moderator
von City Cobra (Gast)


Lesenswert?

Falk B. schrieb:
> Das letzte empfangene Byte vom UART, nicht 'a'.

Und wenn nichts empfangen wurde?

von Falk B. (falk)


Lesenswert?

@  City Cobra (Gast)

>> Das letzte empfangene Byte vom UART, nicht 'a'.

>Und wenn nichts empfangen wurde?

Der Resetwert 0.

von Yalu X. (yalu) (Moderator)


Lesenswert?

W.S. schrieb:
> Ist aber hier ausnahmsweise mal logisch.
> ...

Ich empfinde das ebenfalls als logisch, aber aus einem ganz anderen
Grund.

Hier ist der entsprechende Absatz im neueren Draft in vollem Wortlaut:

1
An assignment operator stores a value in the object designated by the
2
left operand. An assignment expression has the value of the left operand
3
after the assignment,¹¹¹⁾ but is not an lvalue. The type of an
4
assignment expression is the type the left operand would have after
5
lvalue conversion. The side effect of updating the stored value of the
6
left operand is sequenced after the value computations of the left and
7
right operands. The evaluations of the operands are unsequenced.
8
9
———————————————————
10
¹¹¹⁾ The implementation is permitted to read the object to determine the
11
     value but is not required to, even when the object has volatile-
12
     qualified type.

Ohne die Fußnote liest sich der Satz (der von dem C99-Text wörtlich
übernommen wurde)

1
An assignment expression has the value of the left operand after the
2
assignment, […]

tatsächlich so, als müsste zur Ermittlung des Werts des Zuweisungs-
ausdrucks das Volatile-Objekt (d.h. das Register) nach dem Beschreiben
wieder zurückgelesen werden. Wenn man damit aber wirklich Ernst machen
wollte, müsste konsequenterweise das Register aber auch dann gelesen
werden, wenn der Wert gar nicht benutzt wird, denn als R-Value
verwendete Volatile-Objekte müssen immer gelesen werden, auch wenn ihr
Wert sofort wieder verworfen wird, wie bspw. in

1
  UDR0; // Ausdruck ohne Zuweisung, UDR0 wird trotzdem gelesen

Somit müsste auch in

1
  DDRA = 0xFF;

DDRA nach dem Beschreiben sofort wieder zurückgelesen werden, was
natürlich Humbug wäre und von den Normierern sicher auch nicht so
gewollt war.

IMHO wäre in dem Satz

1
An assignment expression has the value of the left operand after the
2
assignment, […]

eindeutiger, wenn man "value of the left operand" durch "value assigned
to the left operand (after type conversion, if required)" ersetzt hätte.
Dann wäre klar, dass das Register nach einer Zuweisung nie zurückgelesen
wird, egal ob der Wert der Zuweisung anschließend noch benutzt wird oder
nicht. Aber mich fragt ja keiner ;-)


In früheren Drafts, wo die Fußnote noch fehlte, war stattdessen der Typ
des Werts der Zuweisung etwas anders spezifiziert:

1
The type of an assignment expression is the type of the left operand
2
unless the left operand has qualified type, in which case it is the
3
unqualified version of the type of the left operand.

Evtl. sollte das Wegfallen der Qualifier (also insbesondere auch von
volatile) ein Hinweis darauf sein, dass das beschriebene Objekt nicht
gelesen werden muss.

Weil das aber vermutlich kaum jemand so verstanden hat, ist die Fußnote
hinzugefügt worden, die in ihrer Formulierung expliziter ist. Und da die
bestehenden Compiler das Zurücklesen unterschiedlich handhabten, wollte
man nicht die eine oder die andere Variante vorschreiben, sondern ließ
explizit beide zu.

von Falk B. (falk)


Lesenswert?

Naja, C eben. Dieser Käse mit den abstrusen Mehrfachzuweisungen hat doch 
praktisch kaum Sinn. Man muss es ja nicht gleich wie BASCOM machen un 
nur einfachste Operationen mit 2 Variablen pro Berechung erlauben, aber 
diese C-Stunts sind schlicht der Blinddarm der Programmiersprache. Man 
sollte sie nicht (aus)reizen!

: Bearbeitet durch User
von Petter (Gast)


Lesenswert?

Toll ist auch sowas:

a[b] = a[b--];

Da weiß ich nie, ob zuerst subtrahiert wird und dann links noch das alte 
b steht oder nicht...!?

von (prx) A. K. (prx)


Lesenswert?

Falk B. schrieb:
> Naja, C eben. Dieser Käse mit den abstrusen Mehrfachzuweisungen hat doch
> praktisch kaum Sinn.

Sobald eine Zuweisung als Teil eines Ausdrucks verwendet werden kann, 
wird auch eine Mehrfachzuweisung möglich.

Ohne dies wäre es eine völlig andere Sprache geworden. Incr/Decr-Ops 
sind Zuweisungen ziemlich ähnlich, ergeben aber ohne Verwendung in einer 
weiteren Rechnung wie c = *++p; wenig Sinn.

Ebenso wird das verwendet in typischen klassischen C Statements wie
  while ((c = getchar()) != EOF) ...

Dass auch a = b = c = ... möglich ist, ist nur ein Nebeneffekt.

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Petter schrieb:
> Toll ist auch sowas:
>
> a[b] = a[b--];
>
> Da weiß ich nie, ob zuerst subtrahiert wird und dann links noch das alte
> b steht oder nicht...!?

Deshalb bekommst Du auch eine Warnung (irgend was mit sequence-points) 
vom Compiler. Ich hab mir angewöhnt die Warneinstellungen stets auf das 
striktest mögliche zu stellen und zusätzlich alle Warnungen als Fehler 
zu behandeln (bis auf zwei oder drei Ausnahmen) so daß ich gar nicht 
erst in Versuchung komme sowas zu schreiben.

von Alexander T. (Firma: Arge f. t. abgeh. Technologie) (atat)


Lesenswert?

Teo D. schrieb:
> Hier:
> http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/undefinierte_zuweisungen_c

Das trifft aber nicht das hier angesprochene Problem, welches (wie schon 
zutreffend bemerkt) durch Links-/Rechtsassoziativität gelöst wird.

von db8fs (Gast)


Lesenswert?

Petter schrieb:
> Toll ist auch sowas:
>
> a[b] = a[b--];

Ganz besonders toll, wenn b unsigned ist und man das Konstrukt b-- in 
ner Schleife verwenden will...

> Da weiß ich nie, ob zuerst subtrahiert wird und dann links noch das alte
> b steht oder nicht...!?

https://herbsutter.com/2014/12/01/a-quick-poll-about-order-of-evaluation/

von Frank Furz (Gast)


Lesenswert?

Kann man das hier als for-Schleife schreiben, ohne einen größeren 
Datentyp zu verwenden (256 Durchläufe)?
1
uint8_t i = 0;
2
do {
3
  // ...
4
} while (++i > 0);

von Yalu X. (yalu) (Moderator)


Lesenswert?

Klar geht das:
1
  for(uint8_t i=0;; i++) {
2
    // ...
3
    if(i==255)
4
      break;
5
  }

von Frank Furz (Gast)


Lesenswert?

Naja, fällt aber eher in die Kategorie "Vergewaltigung einer 
for-Schleife". Eigentlich schade, dass es mit der normalen Syntax nicht 
geht.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Frank Furz schrieb:
> Naja, fällt aber eher in die Kategorie "Vergewaltigung einer
> for-Schleife".

Ja, da hast du nicht ganz unrecht :)

> Eigentlich schade, dass es mit der normalen Syntax nicht geht.

Der Grund dafür liegt im Wesentlichen darin, dass die For-Schleife eine
kopfgesteuerte Schleife ist. Deren Vorteil liegt darin, dass damit auch
0 Durchläufe möglich sind.

Die Do-While-Schleife ist fußgesteuert, damit sind auch 256 Durchläufe
möglich. Dafür muss man aber auf die Möglichkeit von 0 Durchläufen
verzichten. Auch das von mir gepostete Beispiel mit dem bedingten break
am Ende der For-Schleife ist fußgesteuert.

Ein Schleifentyp, dessen Durchlaufzahl durch einen 8-Bit-Wert definiert
wird und 0 bis einschließlich 256 Durchläufe zulässt, kann es leider
nicht geben.

In Pascal ist die For-Schleife so definiert, dass auch 256 Durchläufe
möglich sind:

1
var
2
  i, n: byte;
3
begin
4
  read(n);
5
  for i:=0 to n do
6
    {...}
7
end.

Hier dreht die Schleife (n+1)-mal, für n=255 also 256-mal. 0 Durchläufe
damit sind aber nicht möglich, da dafür n=-1 sein müsste, was außerhalb
des Wertebereichs von byte liegt.

Bei Schleifen mit variabler Durchlaufzahl ist in der Praxis sehr oft die
Möglichkeit von 0 Durchläufen gefordert. Der andere Fall, dass über den
kompletten Wertebereich des Zählerdatentyps iteriert werden soll, kommt
eher seltener vor. Eigens dafür einen fußgesteuerten Zählschleifentyp
vorzusehen, wäre übertrieben.

Deswegen muss man diesen Fall in C eben mit einer Do-While-Schleife
erschlagen (oder für die Zählvariable den nächstgrößeren Datentyp
wählen).

von Rasputin (Gast)


Lesenswert?

Nach meiner Logik ist
1
a = b = c;

nicht das gleiche wie
1
b = c;
2
a = c;

sondern eher:
1
temp = c;
2
b = temp;
3
a = temp;

und zwar aus der Überlegung
1
a = b + c;

a ist das Ergebnis der Operation "Addition c plus b".

also müsste bei
1
a = b = c;

a müsste das Ergebnis der Operation "Zuweisung c zu b" sein. Also der 
Wert den c hatte zum Zeitpunkt der Zuweisung zu b.

von Eric B. (beric)


Lesenswert?

Frank Furz schrieb:
> Kann man das hier als for-Schleife schreiben, ohne einen größeren
> Datentyp zu verwenden (256 Durchläufe)?
>
> uint8_t i = 0;
> do {
>   // ...
> } while (++i > 0);
1
uint8_t j = 1;
2
for (i = 0;  i != j; i++) {
3
  j = 0;
4
  // ...
5
}

oder
1
uint8_t i = 0;
2
// ...
3
for(++i; i != 0; i++)
4
{
5
  // ...
6
}

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

@Eric B.:

Deine erste Lösung gefällt mir sehr gut. Der GCC optimiert dabei sogar
das j einschließlich der Zuweisung j=0 weg, so dass am Ende exakt der
gleiche, optimale Code wie bei der Do-While-Schleife herauskommt.

Bei deiner zweiten Lösung macht die Schleife aber doch nur 255
Durchläufe. Damit der Schleifenrumpf trotzdem 256-mal ausgeführt wird,
hast du ihn einfach noch einmal vor die Schleife kopiert, was ja nicht
so besonders elegant ist. Oder habe ich da etwas falsch verstanden?

von Eric B. (beric)


Lesenswert?

Yalu X. schrieb:
> Bei deiner zweiten Lösung macht die Schleife aber doch nur 255
> Durchläufe. Damit der Schleifenrumpf trotzdem 256-mal ausgeführt wird,
> hast du ihn einfach noch einmal vor die Schleife kopiert, was ja nicht
> so besonders elegant ist. Oder habe ich da etwas falsch verstanden?

Nö, du hast schon richtig verstanden :-)
Es hat den "Vorteil", dass keine extra Variabele benötigt wird. Ich habe 
aber keine Ahnung ob irgendein Compiler die Code-Duplizierung 
wegoptimieren könnte. Vielleicht wenn der duplizierte Code in einer 
inline Funktion gepackt wird.
1
inline void whatever() 
2
{
3
  // ...
4
}
5
6
whatever();
7
for(++i; i != 0; i++)
8
{
9
  whatever();
10
}


Oh, da fällt mir dann noch eine alternative ein:
1
inline void whatever() 
2
{
3
  // ...
4
}
5
6
...
7
for (i = 0; whatever(), (++i != 0) ;)
8
{
9
}
:-D

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Yalu X. schrieb:
> Deine erste Lösung gefällt mir sehr gut.

Mir nicht. Es sei denn ich wäre Schiedsrichter in einem 
Codeobfuscation-Contest. Was spricht degegen einfach den Code 
straightforward so hinzuschreiben wie man ihn meint, in dem Fall also 
eine simple do/while?

von Frank Furz (Gast)


Lesenswert?

Also, in meinen Augen ist alles, was über die normale Syntax einer 
for-Schleife hinausgeht, irgendwie Murks und dann sollte man das Ganze 
als while-do oder do-while ausschreiben. Meine Verwirrung stammte 
tatsächlich daher, dass ich in meiner Jugend exzessiv Pascal 
programmiert habe, wo das Ganze etwas anders ist.
Die Frage ist jetzt: Optimiert es der gcc denn, wenn ich 256 loops 
brauche und aus Leichtsinn/Faulheit eine for-Schleife und uint16_t 
nehme? Mit einem schlauen Compiler kommt ja vielleicht bei der 
for-Variante mit uint16_t der gleiche Code raus wie bei der 
do-while-Variante mit uint8_t...
Habe es nicht ausprobiert...

von Yalu X. (yalu) (Moderator)


Lesenswert?

Bernd K. schrieb:
> Yalu X. schrieb:
>> Deine erste Lösung gefällt mir sehr gut.
>
> Mir nicht. Es sei denn ich wäre Schiedsrichter in einem
> Codeobfuscation-Contest. Was spricht degegen einfach den Code
> straightforward so hinzuschreiben wie man ihn meint, in dem Fall also
> eine simple do/while?

Du hast den Thread nicht gelesen.

Es ging um diese Frage:

Frank Furz schrieb:
> Kann man das hier als for-Schleife schreiben, ohne einen größeren
> Datentyp zu verwenden (256 Durchläufe)?
>
> uint8_t i = 0;
> do {
>   // ...
> } while (++i > 0);

Da m.W. im ISO-Normungsgremium keine Bestrebungen bestehen, in C die
Do-While-Schleife abzuschaffen, ist die Diskussion um den besten Ersatz
dafür natürlich rein hypothetischer Natur ;-)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Frank Furz schrieb:
> Die Frage ist jetzt: Optimiert es der gcc denn, wenn ich 256 loops
> brauche und aus Leichtsinn/Faulheit eine for-Schleife und uint16_t
> nehme?

Nein, nicht einmal dann, wenn die Laufvariable im Schleifenrumpf gar
nicht benutzt wird:
1
  for(uint16_t i=0; i<256; i++) {
2
    PORTB = 1;
3
  }

Das Inkrement wird hier trotzdem als 16-Bit-Operation ausgeführt
(AVR-GCC 6.2.0). Es würde aber nichts gegen so eine Optimierung
sprechen. Vielleicht kommt sie ja in Version 7 :)

Edit:

Auch die Abbruchbedingung i<=255 statt (i<256) ändert nichts daran.

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Frank Furz schrieb:
> Die Frage ist jetzt: Optimiert es der gcc denn, wenn ich 256 loops
> brauche und aus Leichtsinn/Faulheit eine for-Schleife und uint16_t
> nehme?

Man nehme folgenden Test:
1
#include <stdint.h>
2
3
extern uint8_t *data;
4
5
uint8_t
6
sum_data(void)
7
{
8
   uint8_t result = 0;
9
#ifdef DOWHILE
10
   uint8_t i = 0;
11
   do {
12
      result += data[i];
13
   } while (++i != 0);
14
#else
15
   for (int i = 0; i < 256; i++)
16
      result += data[i];
17
#endif
18
   return result;
19
}

Erste Version, mit -DDOWHILE compiliert:
1
sum_data:
2
/* prologue: function */
3
/* frame size = 0 */
4
/* stack size = 0 */
5
.L__stack_usage = 0
6
        lds r20,data
7
        lds r21,data+1
8
        ldi r25,0
9
        ldi r24,0
10
.L2:
11
        movw r30,r20
12
        add r30,r25
13
        adc r31,__zero_reg__
14
        ld r18,Z
15
        add r24,r18
16
        subi r25,lo8(-(1))
17
        brne .L2
18
/* epilogue start */
19
        ret

Zweite Version, ohne DOWHILE:
1
sum_data:
2
/* prologue: function */
3
/* frame size = 0 */
4
/* stack size = 0 */
5
.L__stack_usage = 0
6
        lds r30,data
7
        lds r31,data+1
8
        movw r18,r30
9
        inc r19
10
        ldi r24,0
11
.L2:
12
        ld r25,Z+
13
        add r24,r25
14
        cp r30,r18
15
        cpc r31,r19
16
        brne .L2
17
/* epilogue start */
18
        ret

Witzigerweise also komplett anders implementiert, die Variable i
gibt es gar nicht mehr, sondern das Schleifenende wird als Vergleich
des aktuellen Zeigers gegen einen Zielwert realisiert, wobei der
GCC (5.3.0) beim Berechnen des Zielwertes gut geschnallt hat, dass
er für die +256 lediglich das höhere Byte des Zählers inkrementieren
muss.

: Bearbeitet durch Moderator
von Frank Furz (Gast)


Lesenswert?

Vielen Dank für die Mühe!

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.