Was bewirkt, dass ein Zeichen signiert oder unsigniert wird, wenn gcc verwendet wird?
Lesezeit: 7 Minuten
Was bewirkt, wenn a char in C (mit gcc) ist signiert oder unsigniert? Ich weiß, dass die Norm das eine nicht vorschreibt und ich das überprüfen kann CHAR_MIN und CHAR_MAX von limits.h, aber ich möchte wissen, was bei der Verwendung von gcc übereinander auslöst
Wenn ich limits.h von libgcc-6 lese, sehe ich, dass es ein Makro gibt __CHAR_UNSIGNED__ was ein “Standard” -Zeichen mit oder ohne Vorzeichen definiert, aber ich bin mir nicht sicher, ob dies vom Compiler zu (seiner) Erstellungszeit festgelegt wird.
Ich habe versucht, GCCs vordefinierte Makros mit aufzulisten
$ gcc -dM -E -x c /dev/null | grep -i CHAR
#define __UINT_LEAST8_TYPE__ unsigned char
#define __CHAR_BIT__ 8
#define __WCHAR_MAX__ 0x7fffffff
#define __GCC_ATOMIC_CHAR_LOCK_FREE 2
#define __GCC_ATOMIC_CHAR32_T_LOCK_FREE 2
#define __SCHAR_MAX__ 0x7f
#define __WCHAR_MIN__ (-__WCHAR_MAX__ - 1)
#define __UINT8_TYPE__ unsigned char
#define __INT8_TYPE__ signed char
#define __GCC_ATOMIC_WCHAR_T_LOCK_FREE 2
#define __CHAR16_TYPE__ short unsigned int
#define __INT_LEAST8_TYPE__ signed char
#define __WCHAR_TYPE__ int
#define __GCC_ATOMIC_CHAR16_T_LOCK_FREE 2
#define __SIZEOF_WCHAR_T__ 4
#define __INT_FAST8_TYPE__ signed char
#define __CHAR32_TYPE__ unsigned int
#define __UINT_FAST8_TYPE__ unsigned char
konnte es aber nicht finden __CHAR_UNSIGNED__
Hintergrund: Ich habe Code, den ich auf zwei verschiedenen Maschinen kompiliere:
Der einzige offensichtliche Unterschied ist also die CPU-Architektur …
…und wir haben auf den Punkt gebracht, warum Zeichen nicht verwendet werden sollten.
– Tyler Durden
28. September 2017 um 19:27 Uhr
Ist char standardmäßig signiert oder unsigned?. Die meisten modernen Implementierungen haben ein signiertes Zeichen, obwohl Sie dies mit einer Option ändern können. Eine bemerkenswerte Ausnahme ist ARM aus Leistungsgründen
Laut dem C11 Standard (lesen n1570), char kann sein signed oder unsigned (Sie haben also tatsächlich zwei Geschmacksrichtungen von C). Was genau es ist, ist implementierungsspezifisch.
gcc hat sogar welche -fsigned-char oder -funsigned-charMöglichkeit die Sie fast nie verwenden sollten (weil das Ändern einige Eckfälle bricht Aufruf Konventionen und ABIs), es sei denn, Sie kompilieren alles neu, einschließlich Ihrer C-Standardbibliothek.
In den meisten Fällen sollten Sie schreiben tragbar C-Code, der nicht von diesen Dingen abhängt. Und Sie können plattformübergreifende Bibliotheken finden (z glatt) um Ihnen dabei zu helfen.
Übrigens gcc -dM -E -x c /dev/null gibt auch __BYTE_ORDER__ usw., und wenn Sie ein vorzeichenloses 8-Bit-Byte möchten, sollten Sie es verwenden <stdint.h> und sein uint8_t (portabler und besser lesbar). Und serienmäßig Grenzen.h definiert CHAR_MIN und SCHAR_MIN und CHAR_MAX und SCHAR_MAX (Sie könnten sie vergleichen, um Gleichheit zu erkennen signed chars-Implementierungen), etc…
Übrigens, Sie sollten sich darum kümmern Zeichenkodierungaber die meisten Systeme verwenden heute UTF-8 überall. Bibliotheken wie libunistring sind hilfreich. Siehe auch dies und bedenke das praktisch gesprochen an Unicode Zeichen codiert in UTF-8 kann sich über mehrere Bytes erstrecken (z char-s).
Der einfachste und portabelste Weg, dieses Problem zu lösen, besteht natürlich darin, einfach zu schreiben, was Sie meinen: signed char oder unsignedwie es der Fall sein mag.
– Kevin
29. September 2017 um 2:53 Uhr
Jonathan Leffler
Die Standardeinstellung hängt von der Plattform und dem nativen Zeichensatz ab. Beispielsweise müssen Computer, die EBCDIC verwenden (normalerweise Mainframes), verwenden unsigned char (oder haben CHAR_BIT > 8), da der C-Standard erfordert, dass Zeichen im grundlegenden Codesatz positiv sind, und EBCDIC Codes wie 240 für die Ziffer 0 verwendet. (C11-Standard, §6.2.5 Typen §2 sagt: Ein als Typ deklariertes Objekt char groß genug ist, um jedes Mitglied des grundlegenden Ausführungszeichensatzes zu speichern. Wenn ein Mitglied des grundlegenden Ausführungszeichensatzes in a gespeichert ist char -Objekt, ist sein Wert garantiert nichtnegativ.)
Sie können steuern, welches Zeichen GCC verwendet -fsigned-char oder -funsigned-char Optionen. Ob das eine gute Idee ist, ist eine separate Diskussion.
Es ist eine gute Idee, wenn Sie, wie es das OP wahrscheinlich tut, Software auf einem PC entwickeln und testen, die später auf einem RaspberryPi läuft.
– Luator
28. September 2017 um 12:54 Uhr
@luator Eine gute Idee ist es, Code zu schreiben, damit es egal ist, ob char signiert ist oder nicht und verwenden int8_t und uint8_t wenn Sie einen vorzeichenbehafteten oder vorzeichenlosen 8-Bit-Wert benötigen.
– Blackjack
28. September 2017 um 13:37 Uhr
Können Sie darauf hinweisen, wo der C-Standard besagt, dass der grundlegende Codesatz positiv sein muss?
– Yakk – Adam Nevraumont
28. September 2017 um 13:39 Uhr
@BlackJack Okay, dem stimme ich zu.
– Luator
28. September 2017 um 14:44 Uhr
msc
Zeichentyp char sein signed oder unsignedje nach Plattform und Compiler.
Die C- und C++-Standards lassen den Zeichentyp char zu unterzeichnet oder ohne Vorzeichen, je nach Plattform und Compiler.
Die meisten Systeme, einschließlich x86 GNU/Linux und Microsoft Windows verwenden signiertes Zeichen,
aber diejenigen basierend auf PowerPC- und ARM-Prozessoren verwenden normalerweise unsigned char.(29)
Dies kann zu unerwarteten Ergebnissen führen, wenn Programme zwischen Plattformen portiert werden, die unterschiedliche Standardeinstellungen für den Zeichentyp haben.
GCC bietet die Optionen -fsigned-char und -funsigned-char um den Standardtyp festzulegen char.
Andere Plattformen werden ähnliche ABI-Standarddokumente haben, die die Regeln spezifizieren, die es verschiedenen C-Compilern ermöglichen, sich auf Aufrufkonventionen, Struct-Layouts und ähnliches zu einigen. (Siehe das x86-Tag-Wiki für Links zu anderen x86-ABI-Dokumenten oder andere Orte für andere Architekturen. Die meisten Nicht-x86-Architekturen haben nur eine oder zwei Standard-ABIs.)
Aus der x86-64 SysV ABI: Abbildung 3.1: Skalare Typen
C sizeof Alignment AMD64
(bytes) Architecture
_Bool* 1 1 boolean
-----------------------------------------------------------
char 1 1 signed byte
signed char
---------------------------------------------------------
unsigned char 1 1 unsigned byte
----------------------------------------------------------
...
-----------------------------------------------------------
int 4 4 signed fourbyte
signed int
enum***
-----------------------------------------------------------
unsigned int 4 4 unsigned fourbyte
--------------------------------------------------------------
...
* Dieser Typ heißt bool in C++.
*** C++ und einige Implementierungen von C lassen Aufzählungen zu, die größer als ein Int sind. Der zugrunde liegende Typ wird in dieser Reihenfolge auf unsigned int, long int oder unsigned long int gestoßen.
Ob char signiert ist oder nicht, wirkt sich in diesem Fall aufgrund einer derzeit nicht dokumentierten Anforderung, auf die clang angewiesen ist, tatsächlich direkt auf die Aufrufkonvention aus: Narrow-Typen werden gemäß dem aufgerufenen Prototyp auf 32 Bit vorzeichen- oder nullerweitert, wenn sie als Funktionsargumente übergeben werden.
gcc:
movsx eax, dil # sign-extend low byte of first arg reg into eax
ret
clang:
mov eax, edi # copy whole 32-bit reg
ret
Auch abgesehen von der Berufungskonvention, C-Compiler müssen zustimmen, damit sie Inline-Funktionen in a kompilieren .h in der gleichen Weise.
Wenn (int)(char)x sich in verschiedenen Compilern für dieselbe Plattform unterschiedlich verhalten würden, wären sie nicht wirklich kompatibel.
gcc hat zwei Kompilierzeitoptionen, die das Verhalten von steuern char:
-funsigned-char
-fsigned-char
Es wird nicht empfohlen, eine dieser Optionen zu verwenden, es sei denn, Sie wissen genau, was Sie tun.
Der Standardwert ist plattformabhängig und wird beim Erstellen von gcc selbst festgelegt. Es wurde aufgrund der besten Kompatibilität mit anderen Tools ausgewählt, die auf dieser Plattform vorhanden sind.
Ein wichtiger praktischer Hinweis ist, dass der Typ eines UTF-8-String-Literals, wie z u8"..."ist ein Array von char, und es muss im UTF-8-Format gespeichert werden. Zeichen im Basissatz sind garantiert äquivalent zu positiven ganzen Zahlen. Jedoch,
Wenn ein anderes Zeichen in einem char-Objekt gespeichert wird, ist der resultierende Wert implementierungsdefiniert, muss aber innerhalb des Wertebereichs liegen, der in diesem Typ dargestellt werden kann.
(In C++ ist der Typ der UTF-8-String-Konstante const char [] und es wird nicht angegeben, ob Zeichen außerhalb des Basissatzes überhaupt numerische Darstellungen haben.)
Wenn Ihr Programm also die Bits einer UTF-8-Zeichenfolge verändern muss, müssen Sie verwenden unsigned char. Andernfalls ist jeder Code, der überprüft, ob die Bytes einer UTF-8-Zeichenfolge in einem bestimmten Bereich liegen, nicht portierbar.
Es ist besser, explizit zu casten unsigned char* als zu schreiben char und erwarte, dass der Programmierer mit den richtigen Einstellungen kompiliert, um das so zu konfigurieren unsigned char. Sie können jedoch a verwenden static_assert() um zu testen, ob die Reichweite von char enthält alle Zahlen von 0 bis 255.
14097100cookie-checkWas bewirkt, dass ein Zeichen signiert oder unsigniert wird, wenn gcc verwendet wird?yes
…und wir haben auf den Punkt gebracht, warum Zeichen nicht verwendet werden sollten.
– Tyler Durden
28. September 2017 um 19:27 Uhr
Ist char standardmäßig signiert oder unsigned?. Die meisten modernen Implementierungen haben ein signiertes Zeichen, obwohl Sie dies mit einer Option ändern können. Eine bemerkenswerte Ausnahme ist ARM aus Leistungsgründen
– phuklv
29. September 2017 um 1:28 Uhr
Es scheint zumindest unter Linux so ziemlich eine 50/50-Aufteilung zwischen signiert und unsigniert zu sein. wiki.debian.org/ArchitectureSpecificsMemo
– Plugwash
8. Juni 2018 um 14:53 Uhr