Forum: Compiler & IDEs Compiler ausrechnen von Konstanten geht schief


von Sven (Gast)


Lesenswert?

Nabend,

ich habe folgendes Problem:
In meinem Code steht:
1
#define F_CPU           8000000UL  
2
#define SoftwarePrescaler (F_CPU*256)/8000000UL

Mit "SoftwarePrescaler" führe ich eine Division durch eines uint_32t 
test:
1
test = test/SoftwarePrescaler ;

Ich erhalte hierbei aber nur das Ergebnis -1.

Ändere ich das define folgendermaßen ab:
1
#define SoftwarePrescaler 256

Ich hätte das #define aber gerne in Abhängigkeit der Taktfrequenz, falls 
ich den Takt einmal ändere (vielfaches von 8Mhz), damit durch einen zu 
kleinen Prescaler keine Variablenüberläufe erfolgen.

erhalte ich bei der Division plausible Werte. Wo liegt der Hase 
begraben?

Danke.

von Stefan E. (sternst)


Lesenswert?

Sven schrieb:
> Wo liegt der Hase
> begraben?

A/B/C  !=  A/(B/C)

von Frager (Gast)


Lesenswert?

> test = test/SoftwarePrescaler ;

Was steht in Test vor der Division?

von Karl H. (kbuchegg)


Lesenswert?

1
#define SoftwarePrescaler ((F_CPU*256)/8000000UL)

von Sven (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> #define SoftwarePrescaler ((F_CPU*256)/8000000UL)

das funktioniert, danke.

Aber wieso funktioniert das nicht:
1
#define SoftwarePrescaler (F_CPU*256)/8000000UL

Sagt das nicht aus, berechnet (F_CPU*256) zuerst und dann teile durch 
8000000UL?

von (prx) A. K. (prx)


Lesenswert?

Weil da letztendlich
 test/(F_CPU*256)/8000000UL
steht, und das ist effektiv
 (test/(F_CPU*256))/8000000UL
und nicht
 test/((F_CPU*256)/8000000UL)

Merke: #define ist pure strohdumme Textersetzung. Immer Klammern um 
Ausdrücke in #define machen.

von Klaus W. (mfgkw)


Lesenswert?

Der Präprozessor macht nur Textersatz.

Also sieh mal an, was in deiner Version gemacht wird:
1
#define F_CPU           8000000UL  
2
#define SoftwarePrescaler (F_CPU*256)/8000000UL
3
4
test = test/SoftwarePrescaler ;

Das wird vom PP ersetzt durch:
1
test = test/ (F_CPU*256)/8000000UL;

Also: test wird durch (F_CPU*256) geteilt, das Ergebnis davon wiederum 
durch 8000000UL.

Du wolltest aber test geteilt haben durch den Wert von 
(F_CPU*256)/8000000UL.
Das würde entsprechen test/(F_CPU*256) und dann mit 8000000UL 
MULTIPLIZIERT!

Deshalb: Den Ersetzungstext von Makros immer klammern! (Ebenso wie evtl. 
alle Argumente...)

von Stefan E. (sternst)


Lesenswert?

Du unterliegst dem klassischen Denkfehler in Bezug auf die Verwendung 
des Präprozessors. Er rechnet nichts "vorab" aus, er macht nur 
Textersetzungen.
1
#define SoftwarePrescaler (F_CPU*256)/8000000UL
2
3
test = test/SoftwarePrescaler ;
Dies bedeutet eben NICHT, dass die eine Division vorab ausgerechnet, und 
dann das Ergebnis für SoftwarePrescaler in die zweite Division 
eingesetzt wird. Das Resultat dieser beiden Zeilen ist, dass der 
Compiler folgendes vorgesetzt bekommt:
1
test = test/(F_CPU*256)/8000000UL ;
Und wie ich schon schrieb: A/B/C != A/(B/C)

von Daniel (Gast)


Lesenswert?

Ok, danke, nun habe ich verstanden.
Dann ist es aber ungeschickt eigentlich, dies als Textersetzung zu 
machen, denn schließlich kostet das ja auch Rechenzeit. Besser wäre es 
doch, wenn ich es dann wirklich "vorab" ausgerechnet als #define 
schreibe oder?

von (prx) A. K. (prx)


Lesenswert?

Daniel schrieb:
> machen, denn schließlich kostet das ja auch Rechenzeit. Besser wäre es
> doch, wenn ich es dann wirklich "vorab" ausgerechnet als #define
> schreibe oder?

Schmeiss die Zeitmaschine an, geh 4 Jahrzehnte zurück und diskutiere das 
mit Dennis Ritchie aus.

von Stefan E. (sternst)


Lesenswert?

Daniel schrieb:
> Dann ist es aber ungeschickt eigentlich, dies als Textersetzung zu
> machen, denn schließlich kostet das ja auch Rechenzeit. Besser wäre es
> doch, wenn ich es dann wirklich "vorab" ausgerechnet als #define
> schreibe oder?

Nein, denn der Compiler macht dann das "vorab" ausrechnen. Das passiert 
nicht erst zur Laufzeit.

von (prx) A. K. (prx)


Lesenswert?

Apropos: Wenn dir diese Falltüren auf die Nerven gehen, dann bist du in 
guter Gesellschaft. Stroustrup hat sich nicht rein zufällig einige Mühe 
gegeben, in C++ möglichst ohne #define auszukommen. Da kannst du
  const unsigned SoftwarePrescaler = (F_CPU*256)/8000000UL;
schreiben und es kommt das raus was du willst.

von Klaus W. (mfgkw)


Lesenswert?

Das ist soweit richtig.

Andererseits kann man auch mit dem PP in C zurechtkommen, wenn man eine 
Handvoll Regeln beachtet.
Wer darauf reinfällt, hat entweder diese Regeln missachtet, oder erst 
gar kein C-Buch gelesen.

(Was ich in diesem Fall auch vermute; sonst wären die Makronamen groß 
geschrieben.)

von (prx) A. K. (prx)


Lesenswert?

Klaus Wachtler schrieb:
> Wer darauf reinfällt, hat entweder diese Regeln missachtet, oder erst
> gar kein C-Buch gelesen.

Allerdings führen diese Regeln auch zu bizarren Makros wie
  #define X do{ ... }while(0)
bei denen es für Nichtexperten ein wenig dauern kann, bis sie den 
tieferen Sinn dieser seltsamen "Schleife" erfasst haben.

von Karl H. (kbuchegg)


Lesenswert?

Daniel schrieb:
> Ok, danke, nun habe ich verstanden.
> Dann ist es aber ungeschickt eigentlich, dies als Textersetzung zu
> machen, denn schließlich kostet das ja auch Rechenzeit. Besser wäre es
> doch, wenn ich es dann wirklich "vorab" ausgerechnet als #define
> schreibe oder?

Aber geh.
Sobald der Präprozessor alle Textersetzungen gemacht hat, kommt da raus

   test = test/((8000000UL*256)/8000000UL) ;

und dann kommt der eigentliche Compiler, der dann den konstanten 
Ausdruck auswertet und durch 256 ersetzt.

Solange du dir bewusst bist, dass der Präprozessor reine Textersetzung 
macht und du deine Makros so schreibst, dass auch dann keine 
Fehlinterpretation möglich ist, solange bist du auf der sicheren Seite. 
Im Zweifelsfall macht man einfach selbst mal die Textersetzungen und 
sieht sich an was da raus kommt.

zb
was ist das Problem bei
1
#define TWICE(x)    2 * x
2
3
  i = TWICE( 3+2 );

offensichtlich wollte der Programmierer ein Makro schreiben, welches 
immer das Doppelte ergibt. 3+2 macht 5, und das Doppelte davon ergibt 
10.
Nur: Im Programm kommt 8 raus.
Warum?

Na, mach mal die Textersetzung.
Ersetze in der Makroverwendung das Makro gegen den Ersatztext und 
tausche gleichzeitig x durch 3+2 aus. Dann steht da
1
   i = 2 * 3+2;
und das ergibt nun mal 8 und nicht die erhofften 10. Denn den 
Präprozessor kümmert nicht, dass das * jetzt nur auf die 3 wirkt und 
nicht auf die 2. Textersetzung! Ohne Rücksicht auf Verluste! Was immer 
dann rauskommt, kommt raus.

Schreibt man es so
1
#define TWICE(x)   2*(x)
2
3
   i = TWICE( 3+2 );

dann stimmts wieder, denn wenn man jetzt die Ersetzung vornimmt
1
  i = 2*(3+2);
ist klar, dass hier in der Berechnung die Absicht des Programmierers, 
nämlich das Doppelte von 3+2 zu erhalten durch die Klammern um x erfüllt 
wird.

Was ist hier das Problem
1
#define MWST 0.2
2
#define PREIS_BRUTTO(x)   x + MWST*x
3
4
5
  double Einzelpreis = 5.0;
6
7
  double Preis_fuer_1_Stueck = PREIS_BRUTTO( Einzelpreis );
8
  double Preis_fuer_5_Stueck = 5 * PREIS_BRUTTO( Einzelpreis );

Warum ist zwar der Preis für 1 Stück korrekt, der Preis für 5 aber 
nicht?

(Wieder: mach die Textersetzungen und du siehst das Problem)


Aber Vorsicht: Nicht alle Probleme lassen sich mit Klammern lösen!
1
#define MAX(x,y)  ((x) > (y) ? (x) : (y))
2
3
   int j = 5, k = 8;
4
5
   i = MAX( j++, k++ );

welche Werte würdest du nach dem Statement für i, j und k erwarten. Wie 
sind die Werte tatsächlich?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:

> Dann steht da
>
>    i = 2 * 3+2;
>
> und das ergibt nun mal 8 und nicht die erhofften 10.

Naja, Billig-Taschenrechner werten

  1 + 2 * 3

ja auch zu 9 aus anstatt zu 7.  Pisa ist in den Enwicklungsabteilungen 
angekommen...

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


Lesenswert?

A. K. schrieb:
> Da kannst du
>   const unsigned SoftwarePrescaler = (F_CPU*256)/8000000UL;
> schreiben und es kommt das raus was du willst.

Geht in C aber mittlerweile genauso, auch wenn es im Gegensatz zu
C++ (oder Pascal) keine echten Konstanten kennt.

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:
> Geht in C aber mittlerweile genauso, auch wenn es im Gegensatz zu
> C++ (oder Pascal) keine echten Konstanten kennt.

Auch wenn das const-Statement im Include-File steht?
Muss dann schon static sein, es sei denn man hätte das in C11 geändert.

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Jörg Wunsch schrieb:
>> Geht in C aber mittlerweile genauso, auch wenn es im Gegensatz zu
>> C++ (oder Pascal) keine echten Konstanten kennt.
>
> Auch wenn das const-Statement im Include-File steht?

Da gehts meines Wissens um was anderes.

Du kannst so ein const nicht dort verwenden, wo ein 
Compile-Time-Constant erwartet wird. Also zb in case-Labels oder was 
ganz besonders unangenehm ist: in anderen Initialisierungen.
1
const unsigned SoftwarePrescaler = (F_CPU*256)/8000000UL;
2
const double Reziprok = 1.0 / SoftwarePrescaler;

von Rolf Magnus (Gast)


Lesenswert?

Oder für Array-Größen:
1
const size_t max_elements = 100;
2
3
int elements[max_elemnts];

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.