Ich will den Wert einer Variable in hexadezimaler Schreibweise anzeigen.
Die Variable ist vom type uint32_t, in meinem konkreten Anwendungsfall
kommen aber niemals Werte gößer als 0xFFFF vor.
Für fast alle Werte erhalte ich die gewünsche Ausgabe von 0x0000 bis
0x7FFF. Sobald jedoch bit 15 gesetzt ist, erhalte ich eine falsche
Ausgabe. Statt 0x8000 erscheint 0xFFFF8000.
Das Problem tritt mit der Library von Atmel auf.
Mit der avr libc die Ubuntu bereit stellt, tritt der Fehler nicht auf.
Mit WinAvr 2010 tritt der Fehöler auch nicht auf.
Ich halte das für einen Bug in der avr library, aber vielleicht irre ich
mich auch. Wie schätzt ihr das ein?
Aber die Variable istdoch schon uint32_t und ich habe lX geschrieben,
was man laut Doku tun soll, wenn die Variable 32bit groß ist.
In einem anderen Anwendungsfall im gleichen Programm kommen auch Werte
im ganzen 32bit Bereich vor, die will ich als 8-Stellige Hexadezimal
Zahl ausgeben, und das funktioniert auch.
Also das geht:
sprintf_P(buffer, PSTR("Wert: 0x%08lX"), value32);
Und das geht nicht:
sprintf_P(buffer, PSTR("Wert: 0x%04lX"), value32);
lX ist doch richtig für 32bit unsigned int, oder habe ich die Doku
falsch verstanden?
Die Vorgeschichte wäre wichtig. Also die von value32. Wie der falsche
Wert da hinein kommt. Denn das ist dein Problem. Du nimmst an, dass
0x8000 drin stünde, es steht aber 0xFFFF8000 drin. Vermutlich durch
sowas wie
uint32_t value32 = ...16-Bit-Rechnung...;
in der irrigen Annahme, die rechte Seite würde aufgrund der linken Seite
bereits in uint32_t gerechnet.
> es steht aber 0xFFFF8000 drin
Dachte ich auch erst, ist aber nicht der Fall. Sonst würde ich mit den
anderen varianten der AVR Library ja die gleiche nicht erwartete Ausgabe
erhalten.
Die Vorgeschichte ist:
Das funktioniert an anderer Stelle auch mit (1<<31). In diesem Fall
setze ich aber nur maximal Bits 0 bis 15, die obere Hälfte bleibt
ungenutzt.
Ein anderer Workaround wäre, nicht lX sondern X zu schreiben. Aber mir
geht es gar nicht darum, einen Workaround zu finden.
Ich möchte gerne klären, ob es sich um einen Bug in Atmels
implementierung der AVR Library handelt (denn in den Variante von Ubuntu
Linux und WinAVR geht es ja wie erwartet).
Werden da auch immer die höherwertigen 16 Bits ignoriert und stattdessen
die unteren 16 Bits vorzeichenbehaftet auf 32 Bits erweitert? Wenn ja,
dann sieht es tatsächlich nach einem Fehler in sprintf_P aus, es sei
denn, in der Dokumentation steht irgendwo geschrieben, dass die Funktion
gar nicht für 32-Bit-Zahlen vorgesehen ist.
holger schrieb:> Besser so:>> value32 |= (1UL<<15);
Wollte dich auch gerade korrigieren, aber du warst schneller :)
Das könnte tatsächlich eine Ursache des Problems sein, auch wenn
Stefan us schrieb:>> es steht aber 0xFFFF8000 drin>> Dachte ich auch erst, ist aber nicht der Fall.
Stefan us schrieb:> Dachte ich auch erst, ist aber nicht der Fall. Sonst würde ich mit den> anderen varianten der AVR Library ja die gleiche nicht erwartete Ausgabe> erhalten.
Bei dem Code würde ich mir an deiner Stelle lieber Sorgen um jene AVR
Libs machen, die nicht das "falsche" Ergebnis zeigen.
Lass mich raten: Funktioniert hatte es in 32/64 Bit Umgebungen.
A. K. schrieb:> Bei dem Code würde ich mir an deiner Stelle lieber Sorgen um jene AVR> Libs machen, die nicht das "falsche" Ergebnis zeigen.
Streng genommen führt 1<<15 auf einem 16-Bit-Prozessor zu undefined
Behavior. Der AVR-GCC liefert aber -32768, wie Holger gerade für GCC
4.3.3 festgestellt hat (und ich für GCC 4.3.6, 4.6.4, 4.7.4, 4.8.3 und
4.9.1 ;-)). Und -32768 auf 32 Bit erweiter ergibt nun mal 0xffff8000.
Interessanter Test. Aber ob das nun ein Bug ist oder nicht, weiss ich
leider immer noch nicht.
Der Format-String 04lX sagt doch, dass die Ausgabe vierstellig sein
soll. Wir haben aber eine achtstellige Ausgabe.
Irgendwie schnalle ich es nicht.
>Der Format-String 04lX sagt doch, dass die Ausgabe vierstellig sein>soll. Wir haben aber eine achtstellige Ausgabe.
Nö, der Formatstring sagt das das Ergebnis mindestens 4 Stellen
mit führenden Nullen hat. Wenn die Zahl mehr als 4 hat werden
auch mehr Stellen ausgegeben.
Yalu X. schrieb:> Und -32768 auf 32 Bit erweiter ergibt nun mal 0xffff8000.
Der Satz im C Standard, der Art der Konvertierung klarstellt, ist
wahrhaft wundervoll formuliert: "Otherwise, if the new type is unsigned,
the value is converted by repeatedly adding or subtracting one more than
the maximum value that can be represented in the new type until the
value is in the range of the new type"
A. K. schrieb:> Der Satz im C Standard, der Art der Konvertierung klarstellt, ist> wahrhaft wundervoll formuliert: "Otherwise, if the new type is unsigned,> the value is converted by repeatedly adding or subtracting one more than> the maximum value that can be represented in the new type until the> value is in the range of the new type"
Habe beim ersten Mal Lesen auch etwas grübeln müssen. Mir fiele
allerdings keine bessere Formulierung ein, ohne inkorrekterweise von der
Darstellung negativer Zahlen im Zweierkomplement auszugehen.
Je mehr ich ausprobiere, umso verwirrender wird es:
1
#include <stdio.h>
2
3
int main(int argc, char** argv) {
4
5
long unsigned int u32Value = (1<<15);
6
printf("Value1 0x%04lX\r\n",u32Value);
7
8
u32Value = (1UL<<15);
9
printf("Value2 0x%04lX\r\n",u32Value);
10
11
u32Value=0xFFFF8000;
12
printf("Value3 0x%04lX\r\n",u32Value);
13
}
Compiliert und ausgeführt unter Ubuntu 32bit mit gcc 4.8.2 (also nix mit
AVR) ergibt:
1
Value1 0x8000
2
Value2 0x8000
3
Value3 0xFFFF8000
Value3 ist also Ok, weil %04lX nur die minimale Länge bestimmt. Aber
Wenn Value 1 und 2 beide zum gleichen Ergebnis führen, dann müsste das
auf dem AVR Mikrocontroller ebenso sein, denn C ist C, unabhängig von
der Register-Breite des Mikrocontroller. Oder etwa nich?
Stefan us schrieb:> denn C ist C, unabhängig von> der Register-Breite des Mikrocontroller. Oder etwa nich?
Gerechnet wird erstmal in int. Und wieviele bits int hat hängt von der
Architektur ab, lediglich daß es mindestens 16 sind ist sicher. Daher:
AVR -> int hat 16 bit -> 1<<15 ist 0x8000 (signed), also mit gesetztem
Vorzeichenbit, und wird dann auf 32 bit erweitert mit sign extension,
daher 0xFFFF8000
PC -> int hat 32 bit -> 1<<15 ist 0x00008000 (signed), also mit NICHT
gesetztem Vorzeichenbit, und wird dann auf 32 bit erweitert, und, da
nicht negativ, kommt 0x00008000 raus.
Stefan us schrieb:> Aber die Variable istdoch schon uint32_t und ich habe lX geschrieben,> was man laut Doku tun soll, wenn die Variable 32bit groß ist.
Klopp die Doku in die Tonne. Der korrekte Modifier für uint32_t ist
PRIu32 bzw. PRIx32 aus inttyes.h.
Wie gesagt ist 1 << 15 in avr-gcc Undefined Behavior, wimre C99 $6.5.7
Clause 3 oder 4; die kenn ich schon auswendig so oft wird das falsch
gemacht.
Übrigens kann Undefined Behavior auch Code ergeben, der deinen
Erwartungen entspricht — was immer diese Erwartungen auch sein mögen :-)
holger schrieb:> Ein unsigned int ist 32Bit auf dem PC
Soweit stimmt es,
> und ein long unsigned int 64Bit.
aber das gilt nur für 64-Bit Unixoide. Nicht aber für 64-Bit Windows und
32-Bit Systeme.
>> long unsigned int u32Value = (1<<15);>>Ein unsigned int ist 32Bit auf dem PC und ein>long unsigned int 64Bit.
Und nochmal verdammt;) Ich sollte schlafen gehen.
(1<<15) wird als int ausgeführt.
Beim AVR GCC ist int 16 Bit. Beim PC ist int grösser 16 Bit.
Es ist also völlig Banane was du da auf dem PC machst.
Es passt nicht zum AVR.
Yalu X. schrieb:> Streng genommen führt 1<<15 auf einem 16-Bit-Prozessor zu undefined> Behavior.
Angelehnt an manche Optimierungsüberraschung neuerer GCC Versionen wär
natürlich denkbar, dass ein Compiler "|= 1<<15" schlicht weglässt, weil
er den Unsinn sieht. Und wenn er dann schon weiss, dass das Ergebnis
undefiniert ist, dann ist er recht frei, noch einen draufzusetzen. Aber
ein konsequentes Szenario, wie bei solchem Compilerdenksport
ausgerechnet 0x00008000 rauskommen sollte, sehe ich grad nicht.
Auf meinem Computer ist unsigned long int 32bit groß. Ich nutze 32bit
Linux. Für 64bit müsste ich long long schreiben.
Wenn (1<<15) als int ausgeführt wird, warum ergibt dann (1<<16) die
Ausgabe 0x10000 (sowohl unter Linux als auch auf dem AVR)?
1
#include <stdio.h>
2
#include <stdint.h>
3
int main(int argc, char** argv) {
4
uint32_t value1 = 0;
5
value1 |= (1<<15);
6
printf("Value1 0x%04lX\r\n",value1);
7
8
uint32_t value2 = 0;
9
value2 |= (1<<16);
10
printf("Value2 0x%04lX\r\n",value2);
11
12
unsigned long int value3 = 0;
13
value3 |= (1<<15);
14
printf("Value3 0x%04lX\r\n",value3);
15
16
unsigned long int value4 = 0;
17
value4 |= (1<<16);
18
printf("Value4 0x%04lX\r\n",value4);
19
20
uint64_t value5 = 0;
21
value5 |= (1<<15);
22
printf("Value5 0x%04lX\r\n",value5);
23
24
uint64_t value6 = 0;
25
value6 |= (1<<16);
26
printf("Value6 0x%04lX\r\n",value6);
27
28
uint64_t value7 = 0;
29
value7 |= (1<<31);
30
printf("Value7 0x%04lX\r\n",value7);
31
32
uint64_t value8 = 0;
33
value8 |= (1<<32);
34
printf("Value8 0x%04lX\r\n",value8);
35
}
Ausgabe:
1
Value1 0x8000
2
Value2 0x10000
3
Value3 0x8000
4
Value4 0x10000
5
Value5 0x8000
6
Value6 0x10000
7
Value7 0x80000000
8
Value8 0x0000
Value 2 4 und 6 lassen vermuten, dass der Ausdruck (1<<16) wohl als
32bit ausgeführt wird.
Value 8 hingegen zeigt, dass die Sache mit 64 bit nicht mehr
funktioniert.
Ich werde dieses Programm demnächst mal auf einem AVR ausführen (geht
gerade nicht) und schauen, wo der sich anders verhält.
Die Theorie vom "undefined behaviour" scheint wohl richtig zu sein.
Stefan us schrieb:> Die Theorie vom "undefined behaviour" scheint wohl richtig zu sein.
Die ist zwar richtig, aber nicht die Ursache. Das ist vielmehr die
Vorzeichenerweiterung auf 32-bit im Rahmen der Konvertierung zu
uint32_t.
Stefan us schrieb:> Wenn (1<<15) als int ausgeführt wird, warum ergibt dann (1<<16) die> Ausgabe 0x10000 (sowohl unter Linux als auch auf dem AVR)?
[...]
> Ich werde dieses Programm demnächst mal auf einem AVR ausführen (geht> gerade nicht) und schauen, wo der sich anders verhält.
Widersprichst du dir da nicht? Hast du das jetzt auf einem AVR
ausgeführt oder nicht?
Vielen Danke Leute, ihr habt mir echt geholfen.
@Klaus
> Widersprichst du dir da nicht?
Sorry, das sieht tatsächlich so aus. Jemand anders hat den Test mit dem
AVR Compiler gemacht, ich hingegen compiliere unter Ubuntu Linux. Leider
habe ich gerade keine Hardware vorliegen, auf der ich das gesamte
Programm unverändert am Stück ausführen könnte.
Ihr habt mir jedoch geholfen, herauszufinden, dass der Fehler nicht beim
sprintf() liegt, sondern bei der Schiebe-Operation. Nun ist es natürlich
leicht, das Programm auf die wenigen relevanten Codezeilen zu
reduzieren.
An dieser Stelle verhalten sich unterschiedliche Versionen des Compilers
tatsächlich unterschliedlich. Und zu meiner Überraschung musste ich
feststellen, dass das Programm auf einem 32Bit Rechner sich wiederum
etwas anders verhält.
Wenn ich den Ausdruck auf (1UL<<15) ändere, verhält sich das Programm
auf allen mir verfügbaren Plattformen gleich und wie erwartet. Ich warte
nun auf Feedback meines Anwenders, ob er damit auch zufrieden ist.
@Holger
> Beim AVR GCC ist int 16 Bit. Beim PC ist int grösser 16 Bit.
Ich war ja ganz kurz davor, Dir vehement zu widersprechen. Denn ich war
felsenfest davon überzeugt, dass die Datentypen in C immer gleich groß
sind, egal auf welcher Plattform das Programm läuft. Aber du has es
bewiesen und ich habe den feinen aber gemeinen Unterschied ja auch
selbst wenige Minuten zuvor (mir selbst) demonstriert.
Also an alle nochmal besten Dank. Habt mir prima geholfen.
Stefan us schrieb:> Denn ich war> felsenfest davon überzeugt, dass die Datentypen in C immer gleich groß> sind, egal auf welcher Plattform das Programm läuft.
Aber gerade das ist doch eine Eigenschaft von C.
Aus diesem Grund wurde ja auch stdint.h eingeführt.
Wenn dies in deinem Lehrmaterial nicht angesprochen wurde, dann solltest
du vermittelte Wissen über C anzweifeln.
Und wie schon mal weiter oben angemerkt wurde, ist der richtige
Formatspecifier (für hex-Ausgabe) für uint32_t dann PRIx32
Stefan us schrieb:> Und mir ist wichtig, dass die Buchstaben groß ausgegeben werden. Also> "lX" und dafür gibt es kein passendes Makro.
PRIX32 ist aber im Standard definiert. Und steht auch in dem Link von
Frank.