Compiler ausrechnen von Konstanten geht schief

Gast #2875595
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.
#2875635
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...)
#2875638
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)
Gast #2875644
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?
#2875660
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.
#2875668
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.)
#2875683
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?
#2875767
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;

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