Mein Problem:
Ich habe einen Tag nach einem Fehler gesucht, der beim Compilieren mit
einer älteren Compilerversion nicht aufgetreten ist. Und zwar ging es um
eine Leftshiftoperation die das 19. Bit als Maske zum Vergleich mit
einem 32Bit Wert setzen sollte. Plattform war ein Controller von
Renesas. Mir geht es aber jetzt darum, wo ich NACHLESEN kann, wie der
Compiler das handhabt um in Zukunft Bescheid zu wissen und nicht raten
zu müssen. Insbesondere auch beim AVR-GCC für 8bitter. Oder ist das
keine Sache des Compilers?
Mein code sah so aus:
1
#define WARNUNG 19
2
u3232BitWert=0;
3
...
4
if(32BitWert&(1<<WARNUNG){
5
//Merker
6
}
Mit dem alten Compiler funktionierte das. Mit dem neuen gab es aber
keine Fehlermeldung, sondern die If-Abfrage war einfach nie erfüllt und
"Merker" wurde nie erreicht (vermutlich sogar wegoptimiert).
Abhilfe schafft natürlich ein expliziter cast:
1
#define WARNUNG 19
2
u3232BitWert=0;
3
...
4
if(32BitWert&((u32)1<<WARNUNG){
5
//Merker
6
}
Aber ab wann ist dieser explizite cast nötig?
Dazu muss ich wissen, was ohne den expliziten cast implizit gemacht
wird!
Wenn WARNUNG als 0 bis 7 definiert wird gibt es vermutlich nie Probleme,
da alles in ein Byte passt. Bei einem Shift um 8 muss schon ein 16 Bit
Wert implizit vorausgesetzt werden.
Meine Frage: Wo finde ich die Information, die mir hier Gewissheit
verschafft? Ist das eine Sache des Compilers, der Standardlib, oder wer
legt das fest?
Vielen Dank für Hinweise.
Verunsichert schrieb:
> Meine Frage: Wo finde ich die Information, die mir hier Gewissheit> verschafft? Ist das eine Sache des Compilers, der Standardlib, oder wer> legt das fest?
Das ist eine Sache des C-Standards.
Grob gesagt:
Alles was kleiner als ein int ist, wird implizit zu einem int
umgecastet.
Ganzzahl-Zahlenkonstante sind ohne Datentypangabe immer int, es sei denn
die Zahl passt nicht in einen int, dann ist es implizit ein long.
In
1<<WARNUNG
ist also sowohl 1, als auch WARNUNG (da ja als Zahl zwischen 0 und 7
definiert) ein int.
Jetzt hängt es natürlich davon ab, wie gross ein int auf deinem Compiler
ist. Und das kann sich (im Prinzip) der Compiler aussuchen. Bei einem
generischen int handelt es sich um 'die auf dieser Maschine natürliche
Breite einer Zahl zum rechnen, aber nicht kleiner als 16 Bit'. D.h. ein
int ist mindestens 16 Bit, kann aber auch größer sein (zb auf einer 32
Bit Maschine wird int meistens 32 Bit sein)
Wenn dein Code darauf angewiesen ist, dass bestimmte Datentypen minimale
Längen haben (weil Konstante benutzt werden), die über den
Minimalanforderungen der definierten Datentypen liegen (die findet man
im C-Standard), dann ist man gut beraten, entweder
* die neuen Datentyp-typedefs zu benutzen
int32_t
uint32_t
etc.
* Zahlenkonstante zu casten
das geht zb besonders gut, wenn man mit define arbeitet
#define WARNUNG 19
#define WARNUNG_MASK ((uint32_t)1 << WARNUNG)
if (32BitWert & WARNUNG_MASK ) {
* Wenn man eine Ausgabemöglichkeit hat, evtuell am Programmanfang zu
testen oder ein assert einzubauen
assert( sizeof(int) > 2 )
letzteres geht nur dann vernünftig, wenn sizeof(char) tatsächlich
Bytegröße hat. sizeof(char) ist per Def immer 1. Aber das muss nicht
bedeuten, dass es sich dabei um 1 Byte handelt. Ist zwar meistens so,
aber das ist nicht garantiert.
Vielen Dank für die Tipps!
Würde bei
#define WARNUNG_MASK ((uint32_t)1 << WARNUNG)
auf einem 32Bit System der Cast wegoptimiert? So dass man ihn
sicherheitshalber besser macht?
Wenn es im C-Standard definiert ist, aber der Compiler es anders
handhaben kann, müsste dann nicht beim Compiler dazu auch etwas zu lesen
sein?
Würde auf einem Atmega8 also implizit bei
if (32BitWert & (1<<WARNUNG) {
ein 16Bit Wert für die 1 angenommen werden?
> Würde bei> #define WARNUNG_MASK ((uint32_t)1 << WARNUNG)> auf einem 32Bit System der Cast wegoptimiert?
Was meinst du mit "wegoptimiert?" Wenn der Wert schon 32 Bit breit ist,
macht der Cast doch sowieso nichts.
> Wenn es im C-Standard definiert ist, aber der Compiler es anders> handhaben kann, müsste dann nicht beim Compiler dazu auch etwas zu lesen> sein?
Ja. Die ISO-Norm schreibt auch vor, daß es in der Dokumentation des
Compilers stehen muß.
> Würde auf einem Atmega8 also implizit bei> if (32BitWert & (1<<WARNUNG) {> ein 16Bit Wert für die 1 angenommen werden?
"angenommen" würde ich nicht sagen. Er definiert einfach, daß der Wert
16 Bit breit ist.
Verunsichert schrieb:
> Vielen Dank für die Tipps!>> Würde bei> #define WARNUNG_MASK ((uint32_t)1 << WARNUNG)> auf einem 32Bit System der Cast wegoptimiert? So dass man ihn> sicherheitshalber besser macht?
:-)
Du kannst davon ausgehen, dass KEINE Erweiterung auf 32 Bit stattfindet,
wenn der Zahlenwert schon als 32 Bit Wert vorliegt. Und etwas anderes
macht ja dieser Cast nicht.
> Wenn es im C-Standard definiert ist, aber der Compiler es anders> handhaben kann,
langsam.
Der Standard verlangt gewisse Minima. Der Compiler kann diese Minima
übertreffen. Daher ist es gut 2 Dinge zu kennen
* was sind die Minima
* inwieweit übertrifft der Compiler diese Minima
Dazu kommt, dass COmpiler auch oft über Commandline Switches verfügen,
mit denen die 'Minima' abgesenkt werden können. So kann man den avr-gcc
per Commandline Switch dazu zwingen, einen int als 8-Bit int
aufzufassen. Das kann man benutzen, man muss allerdings dann sehr genau
wissen was man tut. Stichwort: Libraries wurden zb ohne diesen Switch
erstellt. Ein malloc erwartet trotzdem 16 Bit.
> müsste dann nicht beim Compiler dazu auch etwas zu lesen> sein?
In den Header Files stehen die Werte drinnen.
> Würde auf einem Atmega8 also implizit bei> if (32BitWert & (1<<WARNUNG) {> ein 16Bit Wert für die 1 angenommen werden?
Mit dem avr-gcc: ja
Vielen vielen Dank!
Auch wenn es sicherlich Basics sind und viele müde darüber lächeln
mögen, mir hat die Erklärung wirklich sehr geholfen.
Das war eine wichtige Lektion, die ich mir merken werde.
Nachtrag:
Insbesondere weil einen das in vielen Fällen gar nicht kümmert oder
kümmern muss. Ich bin bisher immer so hingekommen, ohne mich damit
auseinander zu setzen.
Verunsichert schrieb:
> Nachtrag:> Insbesondere weil einen das in vielen Fällen gar nicht kümmert oder> kümmern muss. Ich bin bisher immer so hingekommen, ohne mich damit> auseinander zu setzen.
Die von dir geschliderte Problematik tritt auf Desktopsystemen eher
selten auf.
Die von dir beobachtete Problematik tritt meistens auf, wenn man den
Compilerhersteller insgesamt wechselt. Seltener kommt es zu solchen
Problemen, wenn man auf eine neuere Version des COmpilers wechselt. Ich
kenne keinen Compiler, bei dem bei einem Versionssprung die Datentypen
kürzer wurden. Wenn, dann wurden sie länger.
Was natürlich aber sein kann:
Optimizer werden ständig verbessert. Programmiert man daher am
C-Standard vorbei und hat Glück dass der Optimizer in der alten Version
genau das erzeugt hat, was man ungerechtfertigterweise erwartet hat,
dann landet man bei einem neueren Optimizer irgendwann in der Breduille.
Und dann gibt es natürlich auch noch den Fall, dass der Compiler
fehlerhaft war und das in einer neuen Version korrigiert wurde.
Ich sags ja immer, uns fehlt ein Compiler, bei dem:
* ein char 13 Bit hat
* short, int und long alle 5 Bytes breit sind
* die Integer-Darstellung eines Zeigers einer zufälligen Zahl entspricht
(insbesondere (char *)((int)zeiger + 1) nicht aufs nächste Zeichen
zeigt)
* ...
:-}