mikrocontroller.net

Forum: Compiler & IDEs etwas akademische Frage zu C


Autor: Micha (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter II (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)

Autor: Teo Derix (teoderix)
Datum:

Bewertung
0 lesenswert
nicht lesenswert

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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:

a = b += c = bla;

folgendermaßen auseinanderpfriemeln:

c = bla;
b += c;   // d.h. b = bla + c;
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.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ;-)

Autor: Micha (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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!

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

Bewertung
0 lesenswert
nicht 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:
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. ä.:
DDRA = DDRB = DDRC = DDRD = DDRE = DDRF = DDRG = 0xFF;

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

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
  x = UDR0 = 'a';
Was steht nun in x?

Autor: City Cobra (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter D. schrieb:
> Was steht nun in x?

nicht definiert oder "0" ?

Autor: Falk Brunner (falk)
Datum:

Bewertung
1 lesenswert
nicht 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!

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@  Peter Dannegger (peda)

>  x = UDR0 = 'a';

>Was steht nun in x?

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

Autor: Frank M. (ukw) (Moderator) Benutzerseite
Datum:

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

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

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

Bewertung
0 lesenswert
nicht 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. :)

Autor: Micha (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hilfe! Ich will hier raus!!!

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
2 lesenswert
nicht 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:

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

Also besser die Zuweisungen einzeln hinschreiben.

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

Bewertung
0 lesenswert
nicht 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.

Autor: Hungerkünstler (Gast)
Datum:

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

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

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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! ;-)

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
Jörg W. schrieb:
> Nö, es war wirklich 0x3F.

Welche Version?

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.

Folgender C-Code

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

ergibt mit GCC 5.2.0:

  ldi r24,lo8(-1)
  out 0x13,r24
  out 0x10,r24
  out 0xd,r24
  out 0xa,r24
  out 0x7,r24
  out 0x4,r24
  out 0x1,r24

  ldi r24,lo8(97)
  sts 198,r24
  sts x,r24

Aber wie gesagt: Man sollte sich nicht darauf verlassen.

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

Bewertung
0 lesenswert
nicht 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.

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Frank M. (ukw) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: W.S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Frank M. (ukw) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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
Autor: City Cobra (Gast)
Datum:

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

Und wenn nichts empfangen wurde?

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@  City Cobra (Gast)

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

>Und wenn nichts empfangen wurde?

Der Resetwert 0.

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
1 lesenswert
nicht 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:

An assignment operator stores a value in the object designated by the
left operand. An assignment expression has the value of the left operand
after the assignment,¹¹¹⁾ but is not an lvalue. The type of an
assignment expression is the type the left operand would have after
lvalue conversion. The side effect of updating the stored value of the
left operand is sequenced after the value computations of the left and
right operands. The evaluations of the operands are unsequenced.

———————————————————
¹¹¹⁾ The implementation is permitted to read the object to determine the
     value but is not required to, even when the object has volatile-
     qualified type.

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

An assignment expression has the value of the left operand after the
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

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

Somit müsste auch in

  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

An assignment expression has the value of the left operand after the
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:

The type of an assignment expression is the type of the left operand
unless the left operand has qualified type, in which case it is the
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.

Autor: Falk Brunner (falk)
Datum:

Bewertung
1 lesenswert
nicht 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
Autor: Petter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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...!?

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht 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
Autor: Bernd K. (prof7bit)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Alexander T.-Z. (Firma: Arge f. t. abgeh. Technologie) (atat)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Teo D. schrieb:
> Hier:
> http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81...

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

Autor: db8fs (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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-abo...

Autor: Frank Furz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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);

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klar geht das:
  for(uint8_t i=0;; i++) {
    // ...
    if(i==255)
      break;
  }

Autor: Frank Furz (Gast)
Datum:

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

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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:

var
  i, n: byte;
begin
  read(n);
  for i:=0 to n do
    {...}
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).

Autor: Rasputin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nach meiner Logik ist
a = b = c;

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

sondern eher:
temp = c;
b = temp;
a = temp;

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

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

also müsste bei
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.

Autor: Eric B. (beric)
Datum:

Bewertung
0 lesenswert
nicht 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);
uint8_t j = 1;
for (i = 0;  i != j; i++) {
  j = 0;
  // ...
}

oder
uint8_t i = 0;
// ...
for(++i; i != 0; i++)
{
  // ...
}

: Bearbeitet durch User
Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Eric B. (beric)
Datum:

Bewertung
0 lesenswert
nicht 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.
inline void whatever() 
{
  // ...
}

whatever();
for(++i; i != 0; i++)
{
  whatever();
}


Oh, da fällt mir dann noch eine alternative ein:
inline void whatever() 
{
  // ...
}

...
for (i = 0; whatever(), (++i != 0) ;)
{
}
:-D

: Bearbeitet durch User
Autor: Bernd K. (prof7bit)
Datum:

Bewertung
-1 lesenswert
nicht 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?

Autor: Frank Furz (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
1 lesenswert
nicht 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 ;-)

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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:
  for(uint16_t i=0; i<256; i++) {
    PORTB = 1;
  }

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
Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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:
#include <stdint.h>

extern uint8_t *data;

uint8_t
sum_data(void)
{
   uint8_t result = 0;
#ifdef DOWHILE
   uint8_t i = 0;
   do {
      result += data[i];
   } while (++i != 0);
#else
   for (int i = 0; i < 256; i++)
      result += data[i];
#endif
   return result;
}

Erste Version, mit -DDOWHILE compiliert:
sum_data:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
        lds r20,data
        lds r21,data+1
        ldi r25,0
        ldi r24,0
.L2:
        movw r30,r20
        add r30,r25
        adc r31,__zero_reg__
        ld r18,Z
        add r24,r18
        subi r25,lo8(-(1))
        brne .L2
/* epilogue start */
        ret

Zweite Version, ohne DOWHILE:
sum_data:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
        lds r30,data
        lds r31,data+1
        movw r18,r30
        inc r19
        ldi r24,0
.L2:
        ld r25,Z+
        add r24,r25
        cp r30,r18
        cpc r31,r19
        brne .L2
/* epilogue start */
        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
Autor: Frank Furz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank für die Mühe!

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.