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:

Desktop-PC:

  • Debian GNU/Linux 9.1 (gestreckt)
  • gcc-Version 6.3.0 20170516 (Debian 6.3.0-18)
  • Intel(R) Core(TM) i3-4150
  • libgcc-6-dev: 6.3.0-18
  • char ist unterschrieben

Himbeer-Pi3:

  • Raspbian GNU/Linux 9.1 (Stretch)
  • gcc-Version 6.3.0 20170516 (Raspbian 6.3.0-18+rpi1)
  • ARMv7-Prozessor Rev. 4 (v7l)
  • libgcc-6-dev: 6.3.0-18+rpi
  • char ist unsigniert

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

    – 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

Benutzeravatar von Basile Starynkevitch
Basile Starynkevitch

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.

Etwas Prozessoren und Befehlssatzarchitekturen oder Anwendungs-Binärschnittstellen bevorzuge a signed Zeichentyp (Byte) (z. B. weil er sich gut auf einige abbilden lässt Maschinensprache Anweisung), andere Gefallen an unsigned eines.

gcc hat sogar welche -fsigned-char oder -funsigned-char Mö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.

Du könntest benutzen feature_test_macros(7) und <endian.h> (sehen Endian(3)) oder autoconf unter Linux, um zu erkennen, was Ihr System hat.

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

Benutzeravatar von Jonathan Leffler
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

Benutzeravatar von msc
msc

Zeichentyp char sein signed oder unsignedje nach Plattform und Compiler.

Entsprechend Dies Referenzlink:

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.

Benutzeravatar von Peter Cordes
Peter Kordes

Zumindest unter x86-64-Linux ist es definiert durch das x86-64 System V psABI

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.

So für int foo(char c) { return c; }clang wird sich auf die verlassen Anrufer das arg vorzeichenerweitert haben (code + asm dafür und einen Anrufer auf Godbolt).

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.

Quelle.

Benutzeravatar von Davislor
Davislor

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.

1409710cookie-checkWas bewirkt, dass ein Zeichen signiert oder unsigniert wird, wenn gcc verwendet wird?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy