C/C++ Warum unsigned char für binäre Daten verwenden?
Lesezeit: 9 Minuten
nächtliche Spuren
Ist es wirklich notwendig zu verwenden unsigned char binäre Daten wie in einigen Bibliotheken zu halten, die mit Zeichenkodierung oder binären Puffern arbeiten? Um meine Frage zu verstehen, werfen Sie einen Blick auf den folgenden Code –
beide printf's Ausgang 𤭢 richtig, wo f0 a4 ad a2 ist die Codierung für den Unicode-Codepunkt U+24B62 (𤭢) in hex.
Eben memcpy auch die von einem Zeichen gehaltenen Bits korrekt kopiert.
Welche Argumentation könnte möglicherweise für die Verwendung von sprechen unsigned char anstelle einer plain char?
In anderen verwandten Fragen unsigned char ist hervorgehoben, weil es der einzige (Byte/kleinste) Datentyp ist, der garantiert kein Padding durch die C-Spezifikation hat. Aber wie das obige Beispiel gezeigt hat, scheint die Ausgabe nicht durch Auffüllen als solches beeinflusst zu werden.
Ich habe VC++ Express 2010 und MinGW verwendet, um das Obige zu kompilieren. Obwohl VC die Warnung gab
warning C4309: '=' : truncation of constant value
die Ausgabe scheint das nicht widerzuspiegeln.
PS Dies könnte als mögliches Duplikat von gekennzeichnet werden Soll ein Byte-Puffer ein Zeichenpuffer mit oder ohne Vorzeichen sein? aber meine Absicht ist eine andere. Ich frage, warum etwas, das so gut zu funktionieren scheint char eingetippt werden soll unsigned char?
Aktualisieren: Um aus N3337 zu zitieren,
Section 3.9 Types
2 Für jedes Objekt (außer einem Unterobjekt der Basisklasse) des trivial kopierbaren Typs T, unabhängig davon, ob das Objekt einen gültigen Wert des Typs T enthält oder nicht, können die zugrunde liegenden Bytes (1.7), aus denen das Objekt besteht, in ein char-Array kopiert werden oder unsigned char. Wenn der Inhalt des Arrays von char oder unsigned char zurück in das Objekt kopiert wird, soll das Objekt anschließend seinen ursprünglichen Wert behalten.
In Anbetracht der obigen Tatsache und dass sich mein ursprüngliches Beispiel auf einem Intel-Computer befand char standardmäßig auf signed charbin immer noch nicht überzeugt, ob unsigned char sollte gegenüber bevorzugt werden char.
Noch etwas?
Es ist eine etablierte Konvention – warum sollten Sie es anders machen wollen? Gibt es ein bestimmtes Szenario, in dem Sie verwenden müssen char?
– Björn Pollex
30. November 2012 um 9:40 Uhr
Wenn es nur eine Konvention ist, würde ich mich gerne daran halten. Aber gibt es einen technischen, logischen Grund dafür?
– nächtliche Wanderwege
30. November 2012 um 9:41 Uhr
Wenn Sie Funktionen bereitstellen, die zum Bearbeiten von sowohl binären als auch nicht-binären Daten verwendet werden, kann signed char sicherlich bequemer sein. Es ist schmerzhaft, in und aus unsigned char konvertieren zu müssen, wenn Sie mit Strings arbeiten.
– goji
30. November 2012 um 9:42 Uhr
Die von Ihnen verlinkte Frage gibt einen technischen Grund an – das ist ziemlich klar.
– Björn Pollex
30. November 2012 um 9:43 Uhr
@BjörnPollex: sei aber etwas vorsichtig, ifstream ist basic_ifstream<char>nicht basic_ifstream<unsigned char>. Ich weiß nicht, ob sich das auf den gerade vorgenommenen Fix auswirkt oder nicht, aber es ist nicht so einfach wie “in C++ sind Streamdaten unsigned char“. Die Standardstreams sind anderer Meinung.
– Steve Jessop
30. November 2012 um 11:39 Uhr
Jens Gustedt
In C die unsigned char Der Datentyp ist der einzige Datentyp, der alle folgenden drei Eigenschaften gleichzeitig hat
es hat keine Füllbits, dh es tragen alle Speicherbits zum Wert der Daten bei
Keine bitweise Operation, die von einem Wert dieses Typs ausgeht, kann bei der Rückkonvertierung in diesen Typ einen Überlauf, Trap-Darstellungen oder undefiniertes Verhalten erzeugen
es kann andere Datentypen aliasieren, ohne die “Aliasing-Regeln” zu verletzen, das heißt, dass beim Zugriff auf dieselben Daten durch einen Zeiger, der anders typisiert ist, garantiert alle Änderungen angezeigt werden
Wenn dies die Eigenschaften eines “binären” Datentyps sind, den Sie suchen, sollten Sie ihn unbedingt verwenden unsigned char.
Für die zweite Eigenschaft brauchen wir einen Typ, der ist unsigned. Für diese werden alle Umrechnungen mit Modulo-Arithmetik, hier Modulo, definiert UCHAR_MAX+1, 256 in den meisten 99% der Architekturen. Alle Konvertierung von breiteren Werten zu unsigned char entspricht dabei nur dem Abschneiden auf das niederwertigste Byte.
Die beiden anderen Zeichentypen funktionieren im Allgemeinen nicht gleich. signed char ist sowieso signiert, daher ist die Konvertierung von Werten, die nicht dazu passen, nicht gut definiert. char ist nicht auf signiert oder nicht signiert festgelegt, aber auf einer bestimmten Plattform, auf die Ihr Code portiert wird, kann er signiert sein, auch wenn er auf Ihrer nicht signiert ist.
Sehr neutral und sachlich. +1
– Prof. Falken
30. November 2012 um 13:00 Uhr
Können Sie die zweite Eigenschaft besser erklären oder bitte ein Beispiel geben?
– sop
8. Juni 2016 um 9:06 Uhr
“es kann andere Datentypen aliasieren, ohne die “Aliasing-Regeln” zu verletzen” Dies gilt auch für char zu.
– Kalmarius
27. Juli 2019 um 18:31 Uhr
@Calmarius Und wenn char signiert ist, nur zwei hinzufügen char Werte können überlaufen und zu undefiniertem Verhalten führen.
– Andreas Henle
5. Februar 2020 um 0:58 Uhr
Sie werden die meisten Ihrer Probleme bekommen, wenn Sie den Inhalt einzelner Bytes vergleichen:
kann “schlecht” drucken, weil, abhängig von Ihrem Compiler, c[0] wird auf -1 vorzeichenerweitert, was keineswegs dasselbe ist wie 0xff
Lundin
Die Ebene char type ist problematisch und sollte nur für Strings verwendet werden. Das Hauptproblem mit char ist, dass Sie nicht wissen können, ob es signiert oder nicht signiert ist: Dies ist ein implementierungsdefiniertes Verhalten. Das macht char anders als int etc, int ist immer garantiert unterschrieben.
Obwohl VC die Warnung gab … Abschneiden des konstanten Werts
Es sagt Ihnen, dass Sie versuchen, int-Literale in char-Variablen zu speichern. Dies kann mit der Signiertheit zusammenhängen: Wenn Sie versuchen, eine Ganzzahl mit dem Wert > 0x7F in einem signierten Zeichen zu speichern, können unerwartete Dinge passieren. Formal ist dies ein undefiniertes Verhalten in C, obwohl Sie praktisch nur eine seltsame Ausgabe erhalten würden, wenn Sie versuchen würden, das Ergebnis als einen ganzzahligen Wert zu drucken, der in einem (vorzeichenbehafteten) Zeichen gespeichert ist.
In diesem speziellen Fall sollte die Warnung keine Rolle spielen.
BEARBEITEN :
In anderen verwandten Fragen wird unsigned char hervorgehoben, da es der einzige (Byte/kleinste) Datentyp ist, der garantiert kein Padding durch die C-Spezifikation hat.
Theoretisch dürfen alle Integer-Typen außer unsigned char und signed char laut C11 6.2.6.2 “Padding Bits” enthalten:
“Für andere vorzeichenlose Integer-Typen als unsigned char müssen die Bits der Objektdarstellung in zwei Gruppen unterteilt werden: Wertbits und Füllbits (es muss keines der letzteren geben).”
“Für vorzeichenbehaftete Integer-Typen müssen die Bits der Objektdarstellung in drei Gruppen unterteilt werden: Wertbits, Füllbits und das Vorzeichenbit. Es müssen keine Füllbits vorhanden sein; vorzeichenbehaftetes Zeichen darf keine Füllbits haben.”
Der C-Standard ist absichtlich vage und unscharf und erlaubt diese theoretischen Füllbits, weil:
Es erlaubt andere Symboltabellen als die standardmäßigen 8-Bit-Tabellen.
Es ermöglicht implementierungsdefinierte Vorzeichen und seltsame vorzeichenbehaftete ganzzahlige Formate wie das Einerkomplement oder “Vorzeichen und Größe”.
Eine Ganzzahl verwendet möglicherweise nicht unbedingt alle zugewiesenen Bits.
In der realen Welt außerhalb des C-Standards gilt jedoch Folgendes:
Symboltabellen sind mit ziemlicher Sicherheit 8 Bit (UTF8 oder ASCII). Es gibt einige seltsame Ausnahmen, aber saubere Implementierungen verwenden den Standardtyp wchar_t beim Implementieren von Symboltabellen größer als 8 Bit.
Vorzeichen ist immer das Zweierkomplement.
Eine Ganzzahl verwendet immer alle zugewiesenen Bits.
Es gibt also keinen wirklichen Grund, unsigned char oder signed char zu verwenden, nur um einem theoretischen Szenario im C-Standard auszuweichen.
Überprüfen Sie in Bezug auf die zweite Anmerkung die Frage, auf die ich verlinkt habe.
– nächtliche Wanderwege
30. November 2012 um 9:49 Uhr
@Lundin, ganzzahlige Datentypen können aufgefüllt werden Bits nicht bytes. Und ja, unsigned char ist der einzige Typ, der garantiert keine Füllbits hat.
– Jens Gustedt
30. November 2012 um 9:58 Uhr
Also nehme ich an, um das undefinierte Verhalten zu vermeiden, gotcha – c[0] = 0xF0; mit einem unsigned char ist eine gute Idee? Auch wenn char standardmäßig nicht signiert ist (wie auf ARM-Computern), ist sogar der obige Code in Ordnung, aber die Korrektheit ist ab sofort plattformabhängig. Also nochmal, unsigned char sollte für Plattformunabhängigkeit verwendet werden.
– nächtliche Wanderwege
30. November 2012 um 10:00 Uhr
@nightlytrails Okay, jetzt verstehe ich, was du meinst. Ich habe den Beitrag mit einer Erklärung aktualisiert.
– Ludin
30. November 2012 um 10:07 Uhr
@JensGustedt Die meisten 8- oder 16-Bit-MCUs auf dem Markt haben 8-Bit-Befehlssätze. Es ist einfach unpraktisch für sie, Zeichen in Ganzzahlen hochzustufen, obwohl der C-Standard dies erzwingt (die Integer-Hochstufungsregeln). Solche MCUs optimieren normalerweise die gesamte implizite Integer-Promotion weg, bewahren dabei jedoch alle unerwarteten Kuriositäten, die durch die Promotion verursacht werden, wie z. B. eine Änderung der Vorzeichen.
– Ludin
30. November 2012 um 10:50 Uhr
Paul Brandoli
Bytes sind normalerweise als vorzeichenlose 8-Bit breite Integer gedacht.
Jetzt gibt char nicht das Vorzeichen der Ganzzahl an: Bei einigen Compilern kann char vorzeichenbehaftet sein, bei anderen kann es vorzeichenlos sein.
Wenn ich dem von Ihnen geschriebenen Code eine Bitverschiebungsoperation hinzufüge, habe ich ein undefiniertes Verhalten. Der hinzugefügte Vergleich wird auch ein unerwartetes Ergebnis haben.
char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';
c[0] >>= 1; // If char is signed, will the 7th bit go to 0 or stay the same?
bool isBiggerThan0 = c[0] > 0; // FALSE if char is signed!
printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);
Zur Warnung während der Kompilierung: Wenn das Zeichen signiert ist, versuchen Sie, den Wert 0xf0 zuzuweisen, der im signierten Zeichen nicht dargestellt werden kann (Bereich -128 bis +127), sodass es in einen signierten Wert umgewandelt wird (- 16).
Wenn Sie das Zeichen als unsigned deklarieren, wird die Warnung entfernt, und es ist immer gut, einen sauberen Build ohne Warnung zu haben.
Die Vorzeichenhaftigkeit der Ebene char type ist implementierungsdefiniert. Sofern Sie es also nicht mit Zeichendaten zu tun haben (eine Zeichenfolge, die den Zeichensatz der Plattform verwendet – normalerweise ASCII), ist es normalerweise besser, die Signiertheit explizit durch eine der beiden Verwendungen anzugeben signed char oder unsigned char.
Für binäre Daten ist höchstwahrscheinlich die beste Wahl unsigned charinsbesondere wenn bitweise Operationen an den Daten durchgeführt werden (insbesondere Bitverschiebung, die sich bei Typen mit Vorzeichen nicht so verhält wie bei Typen ohne Vorzeichen).
Philipp
Ich frage, warum etwas, das mit char so gut zu funktionieren scheint, unsigned char eingegeben werden sollte?
Wenn Sie Dinge tun, die nicht „richtig“ im Sinne der Norm sind, verlassen Sie sich auf undefiniertes Verhalten. Ihr Compiler macht es vielleicht heute so, wie Sie es wollen, aber Sie wissen nicht, was er morgen macht. Sie wissen nicht, was GCC oder VC++ 2012 tun. Oder auch, ob das Verhalten von externen Faktoren oder Debug/Release-Kompilierungen usw. abhängt. Sobald Sie den sicheren Pfad des Standards verlassen, können Sie in Schwierigkeiten geraten.
Ausruhen
Nun, was nennt man “Binärdaten”? Dies ist ein Haufen Bits, denen von diesem speziellen Teil der Software, der sie “Binärdaten” nennt, keine Bedeutung zugewiesen wurde. Was ist der nächste primitive Datentyp, der die Idee vermittelt, dass einem dieser Bits keine spezifische Bedeutung zukommt? Ich finde unsigned char.
14120500cookie-checkC/C++ Warum unsigned char für binäre Daten verwenden?yes
Es ist eine etablierte Konvention – warum sollten Sie es anders machen wollen? Gibt es ein bestimmtes Szenario, in dem Sie verwenden müssen
char
?– Björn Pollex
30. November 2012 um 9:40 Uhr
Wenn es nur eine Konvention ist, würde ich mich gerne daran halten. Aber gibt es einen technischen, logischen Grund dafür?
– nächtliche Wanderwege
30. November 2012 um 9:41 Uhr
Wenn Sie Funktionen bereitstellen, die zum Bearbeiten von sowohl binären als auch nicht-binären Daten verwendet werden, kann signed char sicherlich bequemer sein. Es ist schmerzhaft, in und aus unsigned char konvertieren zu müssen, wenn Sie mit Strings arbeiten.
– goji
30. November 2012 um 9:42 Uhr
Die von Ihnen verlinkte Frage gibt einen technischen Grund an – das ist ziemlich klar.
– Björn Pollex
30. November 2012 um 9:43 Uhr
@BjörnPollex: sei aber etwas vorsichtig,
ifstream
istbasic_ifstream<char>
nichtbasic_ifstream<unsigned char>
. Ich weiß nicht, ob sich das auf den gerade vorgenommenen Fix auswirkt oder nicht, aber es ist nicht so einfach wie “in C++ sind Streamdatenunsigned char
“. Die Standardstreams sind anderer Meinung.– Steve Jessop
30. November 2012 um 11:39 Uhr