web-dev-qa-db-fra.com

Qu'est-ce qui fait qu'un caractère est signé ou non lors de l'utilisation de gcc?

Quelles sont les causes si un char en C (en utilisant gcc) est signé ou non signé? Je sais que la norme ne dicte pas l'une sur l'autre et que je peux vérifier CHAR_MIN et CHAR_MAX from limits.h mais je veux savoir ce qui se déclenche l'un sur l'autre lors de l'utilisation de gcc

Si je lis limits.h dans libgcc-6, je vois qu'il y a une macro __CHAR_UNSIGNED__ qui définit un caractère "par défaut" signé ou non, mais je ne sais pas si cela est défini par le compilateur au moment de sa construction.

J'ai essayé de lister les makros prédéfinis de GCC avec

$ 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

mais n'a pas pu trouver __CHAR_UNSIGNED__

Contexte: J'ai du code que je compile sur deux machines différentes:

PC de bureau:

  • Debian GNU/Linux 9.1 (stretch)
  • 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 est signé

Raspberry Pi:

  • Raspbian GNU/Linux 9.1 (étirement)
  • gcc version 6.3.0 20170516 (Raspbian 6.3.0-18 + rpi1)
  • Processeur ARMv7 rév 4 (v7l)
  • libgcc-6-dev: 6.3.0-18 + rpi
  • char n'est pas signé

La seule différence évidente est donc l'architecture CPU ...

49
Andy

Selon la norme C11 (lire n157 ), char peut être signed ou unsigned (vous avez donc en fait deux saveurs de C). Ce que c'est exactement, c'est l'implémentation.

Certains processeurs et architectures de jeux d'instructions ou interfaces binaires d'application favorisent un type de caractère signed (octet) (par exemple parce qu'il correspond bien pour certains code machine instruction), d'autres préfèrent un unsigned un.

gcc en a même -fsigned-char ou -funsigned-charoption que vous ne devriez presque jamais utiliser (car sa modification casse certains cas de coin dans conventions d'appel et ABI) à moins que vous ne recompiliez tout, y compris votre norme C bibliothèque .

Vous pouvez utiliser feature_test_macros (7) et <endian.h> (voir endian (3) ) ou autoconf sous Linux pour détecter ce que possède votre système.

Dans la plupart des cas, vous devez écrire du code portable C, qui ne dépend pas de ces choses. Et vous pouvez trouver des bibliothèques multiplateformes (par exemple glib ) pour vous y aider.

BTW gcc -dM -E -x c /dev/null donne également __BYTE_ORDER__ etc, et si vous voulez un octet 8 bits non signé, vous devez utiliser <stdint.h> et son uint8_t (plus portable et plus lisible). Et standard limits.h définit CHAR_MIN et SCHAR_MIN et CHAR_MAX et SCHAR_MAX (vous pouvez les comparer par égalité pour détecter signed chars implémentations), etc ...

BTW, vous devriez vous soucier de encodage de caractères , mais la plupart des systèmes utilisent aujourd'hui TF-8 partout . Des bibliothèques comme libunistring sont utiles. Voir aussi this et rappelez-vous que pratiquement un caractère nicode codé en TF-8 peut s'étendre sur plusieurs octets (c'est-à-dire char- s).

52

La valeur par défaut dépend de la plate-forme et du jeu de codes natif. Par exemple, les machines qui utilisent EBCDIC (ordinateurs centraux généralement) doivent utiliser unsigned char (ou avoir CHAR_BIT > 8) car la norme C requiert que les caractères du jeu de codes de base soient positifs, et EBCDIC utilise des codes comme 240 pour le chiffre 0. (Norme C11, §6.2.5 Types ¶2 dit: Un objet déclaré comme type char est suffisamment grand pour stocker n'importe quel membre du jeu de caractères d'exécution de base. Si un membre du jeu de caractères d'exécution de base est stocké dans un char , sa valeur est garantie non négative. )

Vous pouvez contrôler quel signe GCC utilise avec -fsigned-char ou -funsigned-char options. Que ce soit une bonne idée est une discussion séparée.

41
Jonathan Leffler

Le type de caractère char doit être signed ou unsigned, selon la plate-forme et le compilateur.

Selon ce lien de référence:

Les normes C et C++ permettent au type de caractère char d'être signé ou non signé , selon la plateforme et le compilateur .

La plupart des systèmes, y compris x86 GNU/Linux et Microsoft Windows, utilisent un caractère signé ,

mais ceux basés sur PowerPC et ARM utilisent généralement des caractères non signés . (29)

Cela peut entraîner des résultats inattendus lors du portage de programmes entre des plates-formes qui ont des valeurs par défaut différentes pour le type de caractère.

GCC fournit les options -fsigned-char et -funsigned-char pour définir le type par défaut de char.

12
msc

Sur Linux x86-64 au moins, il est défini par le x86-64 System V psABI

D'autres plates-formes auront des documents de normes ABI similaires qui spécifient les règles qui permettent aux différents compilateurs C de s'entendre sur les conventions d'appel, les dispositions de structure et des trucs comme ça. (Voir le wiki de la balise x86 pour les liens vers d'autres documents ABI x86, ou d'autres emplacements pour d'autres architectures. La plupart des architectures non x86 n'ont qu'un ou deux ABI standard.)

À partir de l'ABI x86-64 SysV: Figure 3.1: Types scalaires

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

* Ce type est appelé bool en C++.

*** C++ et certaines implémentations de C permettent des énumérations plus grandes qu'un int. Le type sous-jacent est remplacé par un entier non signé, un entier long ou un entier long non signé, dans cet ordre.


Que char soit signé ou non affecte en fait directement la convention d'appel dans ce cas, en raison d'une exigence actuellement non documentée sur laquelle clang s'appuie: les types étroits sont sign ou étendus à 32 bits lorsque passé en tant que fonction args , selon le prototype appelé.

Ainsi, pour int foo(char c) { return c; }, clang s'appuiera sur l'appelant pour avoir l'extension de signe l'argument. ( code + asm pour cela et un appelant sur 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

Même en dehors de la convention d'appel, les compilateurs C doivent se mettre d'accord pour compiler les fonctions en ligne dans un .h De la même manière.

Si (int)(char)x Se comportait différemment dans différents compilateurs pour la même plate-forme, ils ne seraient pas vraiment compatibles.

7
Peter Cordes

gcc a deux options de temps de compilation qui contrôlent le comportement de char:

-funsigned-char
-fsigned-char

Il n'est pas recommandé d'utiliser l'une de ces options sauf si vous savez exactement ce que vous faites.

La valeur par défaut dépend de la plate-forme et est fixée lors de la construction de gcc. Il est choisi pour une meilleure compatibilité avec d'autres outils qui existent sur cette plate-forme.

Source .

6
n.m.

Une remarque pratique importante est que le type d'un littéral de chaîne UTF-8, tel que u8"...", Est un tableau de char, et il doit être stocké au format UTF-8. Les caractères de l'ensemble de base sont garantis équivalents aux entiers positifs. cependant,

Si un autre caractère est stocké dans un objet char, la valeur résultante est définie par l'implémentation mais doit être dans la plage de valeurs qui peut être représentée dans ce type.

(En C++, le type de la constante de chaîne UTF-8 est const char [] Et il n'est pas spécifié si les caractères en dehors de l'ensemble de base ont des représentations numériques.)

Par conséquent, si votre programme doit tordre les bits d'une chaîne UTF-8, vous devez utiliser unsigned char. Sinon, tout code qui vérifie si les octets d'une chaîne UTF-8 sont dans une certaine plage ne sera pas portable.

Il est préférable de convertir explicitement en unsigned char* Que d'écrire char et de vous attendre à ce que le programmeur compile avec les bons paramètres pour configurer cela en unsigned char. Cependant, vous pouvez utiliser une static_assert() pour tester si la plage de char inclut tous les nombres de 0 à 255.

1
Davislor