Forum: Compiler & IDEs sprintf gibt zu viele hexadezimale Ziffern aus


von Stefan F. (Gast)


Lesenswert?

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.
1
sprintf_P(buffer, PSTR("Wert: 0x%04lX"), value32);

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?

von CC (Gast)


Lesenswert?

Das hat was mit signed und unsigned zu tun. Lese dich mal in die 
Binärdarstellung ein. Oder caste den Wert mal nach unsigned.

von CC (Gast)


Lesenswert?

...sorry, uint16_t vermutlich eher. Oder mit 0xffff ver-und-en... oder, 
oder, oder ;)

von B e r n d W. (smiley46)


Lesenswert?

Probiers mal so:

sprintf_P(buffer, PSTR("Wert: 0x%04X"), (unsigned int)value32);

von Stefan F. (Gast)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

> 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:
1
uint32_t value32=0;
2
value32 |= (1<<0);
3
value32 |= (1<<1);
4
value32 |= (1<<2);
5
...
6
value32 |= (1<<15);
7
sprintf_P(buffer, PSTR("Wert: 0x%04lX"), value32);

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).

von holger (Gast)


Lesenswert?

>value32 |= (1<<15);

Mach da mal ein UL rein.

value32 |= (1<<15UL);

von Yalu X. (yalu) (Moderator)


Lesenswert?

Probier doch mal folgende Aufrufe aus:
1
sprintf_P(buffer, PSTR("Wert: 0x%08lX"), 0x7fffUL);
2
sprintf_P(buffer, PSTR("Wert: 0x%08lX"), 0x8000UL);
3
sprintf_P(buffer, PSTR("Wert: 0x%08lX"), 0x10000UL);
4
sprintf_P(buffer, PSTR("Wert: 0x%08lX"), 0x12345678UL);
5
sprintf_P(buffer, PSTR("Wert: 0x%08lX"), 0x12348765UL);
6
sprintf_P(buffer, PSTR("Wert: 0x%08lX"), 0x80000000UL);
7
sprintf_P(buffer, PSTR("Wert: 0x%08lX"), 0xffffffffUL);

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.

von holger (Gast)


Lesenswert?

>Mach da mal ein UL rein.
>
>value32 |= (1<<15UL);

Verdammt;)

Besser so:

value32 |= (1UL<<15);

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von holger (Gast)


Lesenswert?

So, grad mal ausprobiert:
1
    char buffer[16];
2
    uint32_t u32Value = 0;
3
    u32Value = (1<<15);
4
    
5
    sprintf(buffer,"Value1 0x%04lX\r\n",u32Value);
6
    usart_write("%s", buffer);
7
8
    u32Value = (1UL<<15);
9
    
10
    sprintf(buffer,"Value2 0x%04lX\r\n",u32Value);
11
    usart_write("%s", buffer);

Ergebnis eindeutig:

Compiliert am Oct 28 2014 um 21:04:02

Compiliert mit GCC Version 4.3.3

Value1 0xFFFF8000
Value2 0x8000

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von holger (Gast)


Lesenswert?

>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.

von (prx) A. K. (prx)


Lesenswert?

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"

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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?

von Tassilo (Gast)


Lesenswert?

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.

von holger (Gast)


Lesenswert?

>    long unsigned int u32Value = (1<<15);

Ein unsigned int ist 32Bit auf dem PC und ein
long unsigned int 64Bit.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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 :-)

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von holger (Gast)


Lesenswert?

>>    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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Klaus R. (klausro)


Lesenswert?

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?

von holger (Gast)


Lesenswert?

1
    char buffer[16];
2
    uint32_t u32Value = 0;
3
    u32Value = (1<<16);
4
    
5
    sprintf(buffer,"Value1 0x%04lX\r\n",u32Value);
6
    usart_write("%s", buffer);
7
8
    u32Value = (1UL<<16);
9
    
10
    sprintf(buffer,"Value2 0x%04lX\r\n",u32Value);
11
    usart_write("%s", buffer);

Ergebnis:

Compiliert am Oct 28 2014 um 22:32:27

Compiliert mit GCC Version 4.3.3

Value1 0x0000
Value2 0x10000

Wie zu erwarten war.

von holger (Gast)


Lesenswert?

Und ne Compiler warning wird auch ausgegeben

main.c:116: warning: left shift count >= width of type

von Stefan F. (Gast)


Lesenswert?

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.

von Dirk B. (dirkb2)


Lesenswert?

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
1
uint32_t u32Value = 0;
2
sprintf(buffer,"Value1 0x%04"PRIx32"\r\n", u32Value);

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Dirk B. schrieb:
> Und wie schon mal weiter oben angemerkt wurde, ist der richtige
> Formatspecifier (für hex-Ausgabe) für uint32_t dann *PRIx32*

Siehe dazu auch:

  http://www.nongnu.org/avr-libc/user-manual/group__avr__inttypes.html

PRIx32 ist demnach für den AVR gleich "lx". Unter Linux (32 oder 64 bit) 
ist es schlicht und einfach "x".

von Stefan F. (Gast)


Lesenswert?

Und mir ist wichtig, dass die Buchstaben groß ausgegeben werden. Also 
"lX" und dafür gibt es kein passendes Makro.

von DirkB (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

Oh, habe ich übersehen. Danke für den Hinweis.

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.