While-Schleife in Assemblercode von GCC erzeugt.

#2999573
Lesenswert?

Hallo zusammen,

momentan sitze über einem Assemblercode, den mir der GCC (Version 4.6.1) 
aus meinem C-Code generiert. Mich treibt es fast in den Wahnsinn, weil 
ich den Fehler nicht finden kann, wieso der Compiler einen Assemblercode 
generiert, der dazu führt, dass meine Funktion nicht ordnungsgemäß 
funktionieren kann. Die Lösung für das Problem habe ich zwar gefunden, 
es wäre aber für mich und möglicherweise auch für andere Nutzer 
interessant, die Gründe für ein solches Compilerverhalten zu kennen.

Mein Problem bezieht sich auf die "while-Schleife", die in Version 1 
nicht mehr im Assemblercode auftaucht, in Version 2 hingegen schon. Zum 
besseren Verständnis habe ich jeweils die komplette Funktion (Start 
einer AD-Wandlung und warten bis diese erfolgt ist) aus dem lss-File 
kopiert.

Zur Zeit wird von mir das Optimierungslevel -O1 verwendet.

1. Die "nicht funktionierende" Version:
1
uint16_t makeConversion(uint8_t channel)
2
{
3
  uint16_t adcResult;
4

5
  ADMUX &= 0xE0; /* Clear channel selection */
6
     298:  97 b1         in  r25, 0x07  ; 7
7
     29a:  90 7e         andi  r25, 0xE0  ; 224
8
     29c:  97 b9         out  0x07, r25  ; 7
9
  ADMUX |= channel; /* Set channel */
10
     29e:  97 b1         in  r25, 0x07  ; 7
11
     2a0:  89 2b         or  r24, r25
12
     2a2:  87 b9         out  0x07, r24  ; 7
13

14
  ADCSRA |= 0x40; /* Start conversion */
15
     2a4:  36 9a         sbi  0x06, 6  ; 6
16
    
17
  while((ADCSRA & 0x40) == 1)
18
     2a6:  86 b1         in  r24, 0x06  ; 6
19
  {
20
    
21
  }
22
  
23
  adcResult = ADC; /* Get result */
24
     2a8:  84 b1         in  r24, 0x04  ; 4
25
     2aa:  95 b1         in  r25, 0x05  ; 5
26
  
27

28
  return(adcResult);
29
}
30
     2ac:  08 95         ret

2. Die funktionierende Version.

1
uint16_t makeConversion(uint8_t channel)
2
{
3
  uint16_t adcResult;
4

5
  ADMUX &= 0xE0; /* Clear channel selection */
6
     298:  97 b1         in  r25, 0x07  ; 7
7
     29a:  90 7e         andi  r25, 0xE0  ; 224
8
     29c:  97 b9         out  0x07, r25  ; 7
9
  ADMUX |= channel; /* Set channel */
10
     29e:  97 b1         in  r25, 0x07  ; 7
11
     2a0:  89 2b         or  r24, r25
12
     2a2:  87 b9         out  0x07, r24  ; 7
13

14
  ADCSRA |= 0x40; /* Start conversion */
15
     2a4:  36 9a         sbi  0x06, 6  ; 6
16
    
17
  while((ADCSRA & 0x40) != 0)
18
     2a6:  36 99         sbic  0x06, 6  ; 6
19
     2a8:  fe cf         rjmp  .-4        ; 0x2a6 <thm5adc_makeConversion+0xe>
20
  {
21
    
22
  }
23
  
24
  adcResult = ADC; /* Get result */
25
     2aa:  84 b1         in  r24, 0x04  ; 4
26
     2ac:  95 b1         in  r25, 0x05  ; 5
27
  
28

29
  return(adcResult);
30
}
31
     2ae:  08 95         ret

Erläuterung zur Funktion:

Es wird eine AD-Wandlung angestoßen und in einer while-Schleife (zur 
Übersichtlichkeit noch ohne timeout) gewartet, bis diese abgeschlossen 
wurde. Laut Datenblatt wird das Bit ADSC immer dann von 1 auf 0 gesetzt, 
wenn eine Wandlung abgeschlossen wurde. Aus diesem Grund warte ich genau 
auf dieses Event in der while-Schleife. Es gibt natürlich zwei 
Möglichkeiten, wie ich auf den Statuswechsel warten kann.
Möglichkeit 1: Warten, solange ADSC auf 1 steht (siehe Version 1)
Möglichkeit 2: Warten, solange ADSC nicht auf 0 steht (siehe Version 2)

Prinzipiell sollte doch daraus derselbe Assemblercode (bis auf die 
Verwendung von SBIS anstelle von SBIC) entstehen. Wo liegt mein 
Denkfehler?

Viele Grüße,

Philipp
#2999609
Lesenswert?

Hallo Thomas,

da hast du völlig recht. Allerdings füge ich einen Timeout in die 
While-Schleife ein. Und weil daher ein weiteres Abbruchkriterium 
dazukommt, dachte ich, dass es übersichtlicher gewesen wäre.


Um diejenigen, die zufällig auf diesen Beitrag stoßen, nicht im Unklaren 
über den von mir gemachten Fehler zu lassen:
1
while((ADCSRA & 0x40) == 1); //Wird NIEMALS erfüllt
2

3
while((ADCSRA & 0x40) == 0x40); //Würde funktionieren besser ist aber die Lösung von Thomas Eckmann
#2999724
Lesenswert?

Jörg Wunsch schrieb:
> Macht man aber nur, wenn man einen Beitrag beim IOCCC einreichen
> möchte. :-)

Nö.

Es kann sein, daß das 0x40 nicht direkt im Text steht, sondern ein 
Define irgendwo ist.
Und nicht ein While den Ausdruck auswertet, sondern er als Returnwert 
von einer anderen Funktion irgendwo auswertet wird.
Dann ist das !! schon sinnvoll, um immer den Wert TRUE zu erzwingen, 
egal welche Bitposition geprüft wird.
1
#define BLA 0x40
2
// nen Haufen Code ...
3
uint8_t testbla()
4
{
5
// Code ...
6
  return !!(blablub & BLA);
7
}
8
// nen Haufen Code ...
9
  var += testbla();

Peter
#2999733
Lesenswert?

Philipp S. schrieb:

> Um diejenigen, die zufällig auf diesen Beitrag stoßen, nicht im Unklaren
> über den von mir gemachten Fehler zu lassen:

Man kann deinen Fehler so zusammenfassen:

       ungleich 0
  und  gleich 1
sind nicht dasselbe

Wie man sich leicht klar machen kann. 2 ist zwar ungleich 0 (diese 
Fragstellung würde man als mit wahr beantworten), aber 2 ist nicht 
gleich 1 (diese Fragestellung würde man also mit falsch beantworten).

In C ist weniger oft mehr. Da im while bzw. im if die Bedingung 
'ungleich 0' schon implizit mit drinnen steckt(*), lässt man als 
Programmierer den am besten weg (wenn es nicht um eine extreme Betonung 
dieses Sachverhalts geht). Alles was man weglassen kann, kann auch nicht 
zu einem Tippfehler führen.

(*) dies ist so zu verstehen, dass while (oder auch if) sich nicht dafür 
interessieren, dass in ihren Klammern ein logischer Ausdruck mit einem 
boolschen Ergebnis steckt. Sie nehmen einfach den angegebenen Ausdruck, 
werten ihn aus und bewerten dieses Ergebnis nach den Regeln der Sprache 
für logische Ausdrücke.

D.h. bei
   while( x )
geht es nicht um den konkreten Aufbau dieses x. Das muss kein Ausdruck 
sein, in dem ein Vergleichsoperator vorkommt. Das ist in C nicht 
gefordert (in anderen Sprachen ist das anders!). Aus Sicht des while ist 
das einfach nur ein arithmetischer Ausdruck, dessen Ergebnis auf "0 oder 
nicht 0" untersucht wird. Ist er nicht 0, dann wird die Schleife 
(erneut) betreten.

In diesem Sinne ist
  while( x != 0 )
eigentlich doppelt gemoppelt. Denn das Ergebnis des Ausdrucks
   x != 0
ist seinerseits wieder eine Zahl: 0 wenn der Vergleich nicht zutrifft 
und 1 wenn der Vergleich zutrifft. Wenn das while daher das Ergebnis 
dieses Vergleichs prüft, dann stellt man fest, dass dieses Ergebnis 
immer mit dem Ergebnis des explizit hingeschriebenen Vergleiches 
übereinstimmt. D.h. diese Schreibweise mit dem expliziten != Vergleich 
ist in allen Belangen identisch zu

   while( x )

Nun kann es natürlich sein, dass man als Programmierer hier eine 
spezielle Betonung auf den Umstand des 'ungleich 0' legen will und dann 
schreibt man den Vergleich auch explizit an. Aber im Grunde ist er nicht 
notwendig, so wie bei

   i = 1 * k;

die Multiplikation mit 1 nicht notwendig ist. Und die schreibt man ja 
auch nicht explizit an, es sei denn man will da eine Betonung drauf 
legen.


Also keine Scheu

  if( PINC & ( 1 << PB4 ) )

ist perfektes C, an dem es erst mal nichts auszusetzen gibt. Es ist 
genauso logisch einwandfrei, wie

   if( isCursorVisible() )
     ...

einwandfrei ist. Denn in dieser Situation würde wohl kaum jemand

   if( isCursorVisible() != FALSE )

schreiben. Ganz im Gegenteil, würde wohl hier der zusätzliche Vergleich 
die meisten Programmierer stutzig machen und sie würden sich fragen, 
warum der da steht und ob es da wohl irgendwelche Sonderfälle gibt, die 
sie nur einfach nicht durchschauen.

In diesem Sinn:
* Vergleichsoperatoren sind in C auch nichts anderes als arithmetisch 
logische Operatoren. Sie bewerten ihr linkes Argument, bewerten ihr 
rechtes Argument, führen die Aktion durch und haben ein numerisches 
Ergebnis. Konzeptionell identisch zu den Operatoren +, -, *, /, |, .... 
und wie sie sonst alle heißen.

* In C ist weniger oft mehr.
#2999740
Lesenswert?

Philipp S. schrieb:
> Und weil daher ein weiteres Abbruchkriterium
> dazukommt, dachte ich, dass es übersichtlicher gewesen wäre.
Finde ich nicht.

while((ADCSRA & 0x40) & !timeout);

Peter Dannegger schrieb:
> Das !! macht aus Wahr eine 1.
Nö.
Wahr, also true, ist immer 1. Das !! macht aus !0 eine 1.

Karl Heinz Buchegger schrieb:
> ist perfektes C, an dem es erst mal nichts auszusetzen gibt.
MISRAtene Normungspopanze regen sich da aber schon drüber auf.

mfg.
Moderator Persönliche Seite #2999762
Lesenswert?

Thomas Eckmann schrieb:
> while((ADCSRA & 0x40) & !timeout);

Oi oi oi. :-)

Peter Dannegger schrieb:
> Dann ist das !! schon sinnvoll, um immer den Wert TRUE zu erzwingen,
> egal welche Bitposition geprüft wird.

Da finde ich aber ((ADCSRA & 0x40) != 0) allemal besser verständlich.
Das erzwingt auch den Wert 1, unabhängig von der Bitposition, und
man kann es lesen, ohne erst drüber nachdenken zu müssen. ;-)

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren