web-dev-qa-db-fra.com

Pourquoi les littéraux de caractères C sont-ils intégrés au lieu de caractères?

En C++, sizeof('a') == sizeof(char) == 1. Cela a un sens intuitif, puisque 'a' est un littéral de caractère et que sizeof(char) == 1 est défini par la norme.

En C cependant, sizeof('a') == sizeof(int). Autrement dit, il semble que les littéraux de caractères C soient en réalité des entiers. Quelqu'un sait-il pourquoi? Je peux trouver beaucoup de mentions de ce caprice, mais aucune explication de son existence.

101
Joseph Garvin

discussion sur même sujet

"Plus spécifiquement, les promotions intégrales. Dans K & R C, il était pratiquement (?) Impossible d'utiliser une valeur de caractère sans la promotion de int en premier, __. Il y avait et il y a toujours des constantes multi-caractères telles que 'abcd' ou cependant Beaucoup tiendront dans un int. "

36
Malx

La question initiale est "pourquoi?"

La raison en est que la définition d'un caractère littéral a évolué et changé, tout en essayant de rester compatible avec le code existant.

Dans les jours sombres de C au début, il n'y avait pas de types du tout. Au moment où j'ai appris à programmer en C, les types avaient déjà été introduits, mais les fonctions n'avaient pas de prototypes pour indiquer à l'appelant quels étaient les types d'argument. Au lieu de cela, il a été normalisé que tout ce qui est passé en tant que paramètre soit la taille d'un int (cela inclut tous les pointeurs), soit un double.

Cela signifiait que lorsque vous écriviez la fonction, tous les paramètres qui n'étaient pas doubles étaient stockés sur la pile en tant qu'entités, quelle que soit la façon dont vous les avez déclarés, et le compilateur a mis du code dans la fonction pour gérer cela pour vous.

Cela rendait les choses un peu incohérentes. Ainsi, quand K & R écrivit son célèbre livre, ils spécifièrent qu'un littéral de caractère serait toujours promu en int dans n'importe quelle expression, et pas seulement en paramètre de fonction.

Lorsque le comité ANSI a normalisé le C pour la première fois, il a modifié cette règle afin qu'un littéral de caractère soit simplement un entier, car cela semblait être un moyen plus simple de parvenir à la même chose.

Lors de la conception de C++, toutes les fonctions devaient posséder des prototypes complets (ceci n'est toujours pas requis en C, bien que ce soit universellement accepté comme bonne pratique). Pour cette raison, il a été décidé qu'un littéral de caractère pouvait être stocké dans un caractère. L'avantage de cela en C++ est qu'une fonction avec un paramètre char et une fonction avec un paramètre int ont des signatures différentes. Cet avantage n'est pas le cas en C.

C'est pourquoi ils sont différents. Évolution...

24
John Vincent

Je ne connais pas les raisons spécifiques pour lesquelles un caractère littéral en C est de type int. Mais en C++, il y a une bonne raison de ne pas suivre ce chemin. Considère ceci:

void print(int);
void print(char);

print('a');

Vous vous attendez à ce que l'appel à imprimer sélectionne la deuxième version prenant un caractère. Avoir un caractère littéral étant un int rendrait cela impossible. Notez que dans C++, les littéraux ayant plusieurs caractères ont toujours le type int, bien que leur valeur soit définie par l'implémentation. Ainsi, 'ab' a le type int, alors que 'a' a le type char

21

en utilisant gcc sur mon MacBook, j'essaie:

#include <stdio.h>
#define test(A) do{printf(#A":\t%i\n",sizeof(A));}while(0)
int main(void){
  test('a');
  test("a");
  test("");
  test(char);
  test(short);
  test(int);
  test(long);
  test((char)0x0);
  test((short)0x0);
  test((int)0x0);
  test((long)0x0);
  return 0;
};

qui lors de l'exécution donne:

'a':    4
"a":    2
"":     1
char:   1
short:  2
int:    4
long:   4
(char)0x0:      1
(short)0x0:     2
(int)0x0:       4
(long)0x0:      4

ce qui suggère qu'un caractère est de 8 bits, comme vous le pensez, mais qu'un caractère littéral est un int.

18
dmckee

À l'époque de l'écriture de C, le langage d'assemblage MACRO-11 du PDP-11 comportait:

MOV #'A, R0      // 8-bit character encoding for 'A' into 16 bit register

Ce genre de chose est assez courant dans le langage d'assemblage: les 8 bits les plus bas contiendront le code de caractère, les autres bits remis à 0, le PDP-11 ayant même:

MOV #"AB, R0     // 16-bit character encoding for 'A' (low byte) and 'B'

Cela fournissait un moyen pratique de charger deux caractères dans les octets inférieur et supérieur du registre à 16 bits. Vous pouvez ensuite les écrire ailleurs, en mettant à jour des données textuelles ou la mémoire d'écran.

Ainsi, l'idée de promouvoir la taille des personnages est tout à fait normale et souhaitable. Mais supposons que vous ayez besoin d'inscrire "A" dans un registre, non pas dans l'opcode codé en dur, mais quelque part dans la mémoire principale contenant:

address: value
20: 'X'
21: 'A'
22: 'A'
23: 'X'
24: 0
25: 'A'
26: 'A'
27: 0
28: 'A'

Si vous voulez lire un «A» de cette mémoire principale dans un registre, lequel lirez-vous?

  • Certaines CPU peuvent uniquement prendre en charge directement la lecture d'une valeur de 16 bits dans un registre de 16 bits, ce qui signifierait qu'une lecture à 20 ou 22 nécessiterait ensuite l'effacement des bits de «X» et, en fonction de la finalité de la CPU, l'une ou l'autre aurait besoin de passer dans l'octet d'ordre faible.

  • Certaines CPU peuvent nécessiter une lecture alignée sur la mémoire, ce qui signifie que l'adresse la plus basse impliquée doit être un multiple de la taille des données: vous pourrez peut-être lire les adresses 24 et 25, mais pas les 27 et 28.

Ainsi, un compilateur générant du code pour obtenir un "A" dans le registre peut préférer perdre un peu de mémoire supplémentaire et coder la valeur sous la forme 0 "A" ou "A" 0 - en fonction de l'endianité, et en s'assurant également qu'elle est alignée correctement ( c'est-à-dire pas à une adresse mémoire impaire).

Mon hypothèse est que C a tout simplement porté ce niveau de comportement centré sur le processeur, en pensant aux constantes de caractère occupant des tailles de registres de mémoire, corroborant l’évaluation commune de C en tant qu ’« assembleur de haut niveau ».

(Voir 6.3.3 à la page 6-25 de http://www.dmv.net/dec/pdf/macro.pdf )

7
Tony Delroy

Je me souviens d'avoir lu K & R et d'avoir vu un extrait de code qui lisait un caractère à la fois jusqu'à ce qu'il atteigne EOF. Étant donné que tous les caractères sont des caractères valides pour être dans un fichier/flux d'entrée, cela signifie que EOF ne peut pas être une valeur de caractère. Ce que le code a fait était de mettre le caractère lu dans un int, puis de tester EOF, puis de le convertir en caractère s'il ne l'était pas.

Je réalise que cela ne répond pas exactement à votre question, mais il serait logique que le reste des littéraux de caractère soit sizeof (int) si le littéral EOF l'était.

int r;
char buffer[1024], *p; // don't use in production - buffer overflow likely
p = buffer;

while ((r = getc(file)) != EOF)
{
  *(p++) = (char) r;
}
6
Kyle Cronin

Je n'ai pas vu de justification à cela (les littéraux de type C étant des types int), mais voici quelque chose que Stroustrup avait à dire à ce sujet (tiré de Design and Evolution 11.2.1 - Résolution de grain fin):

En C, le type d'un littéral de caractère tel que 'a' est int. Étonnamment, donner 'a' type char en C++ ne pose aucun problème de compatibilité . À l'exception de l'exemple pathologique sizeof('a'), chaque construction pouvant être exprimée C et C++ donnent le même résultat.

Donc, pour la plupart, cela ne devrait poser aucun problème.

5
Michael Burr

C'est le comportement correct, appelé "promotion intégrale". Cela peut arriver dans d'autres cas aussi (principalement des opérateurs binaires, si mes souvenirs sont exacts). 

EDIT: Juste pour être sûr, j’ai vérifié ma copie de Programmation en Expert C: Deep Secrets, et j’ai confirmé qu’un caractère littéral ne le faisait pas commencer par un type int . Il est initialement de type char mais quand il est utilisé dans une expression, il est promu dans un int . Ce qui suit est cité dans le livre:

Les littéraux de caractère ont les types int et ils y arrivent en suivant les règles pour la promotion de type char. C'est trop brièvement couvert dans K & R 1, à la page 39 où il est dit:

Chaque caractère dans une expression est converti en un int .... Notez que tous les float dans une expression sont converti en double .... Depuis un L'argument de la fonction est une expression, les conversions de type ont également lieu lorsque les arguments sont passés aux fonctions: dans particulier, car et short deviennent int, float devient double.

1
PolyThinker

Je ne sais pas, mais je suppose que c'était plus facile à mettre en œuvre de cette façon et que cela importait peu. Ce n'est que lorsque le type a été déterminé par C++, quelle fonction serait appelée, qu'il devait être corrigé.

0
Roland Rabien

Je ne le savais pas vraiment ... Avant les prototypes, tout ce qui était plus étroit qu'un int était converti en int quand on l'utilisait comme argument de fonction. Cela peut faire partie de l'explication.

0
Blaisorblade

La raison historique en est que C et son prédécesseur B ont été développés à l’origine sur différents modèles de mini-ordinateurs DEC PDP avec différentes tailles de Word, prenant en charge le format ASCII 8 bits, mais ne pouvant effectuer des opérations arithmétiques que sur les registres. (Pas le PDP-11, cependant; cela est venu plus tard.) Les premières versions de C définissaient int comme étant la taille de mot native de la machine, et toute valeur inférieure à int devait être élargie à int pour pouvoir être passée à ou à partir d'une fonction, ou utilisé dans une expression binaire, logique ou arithmétique, car c'était ainsi que fonctionnait le matériel sous-jacent.

C’est aussi pourquoi les règles de promotion des entiers indiquent toujours que tout type de données inférieur à int est promu à int. Les implémentations en C sont également autorisées à utiliser un complément mathématique au lieu de deux pour des raisons historiques similaires. La raison pour laquelle les caractères octaux échappés et les constantes octales sont des citoyens de première classe par rapport à hex est également due au fait que ces mini-ordinateurs du début du DEC avaient des tailles de mots divisibles en morceaux de trois octets mais pas en quatre octets.

0
Davislor

C’est seulement tangent à la spécification de langue, mais dans le matériel, la CPU n’a généralement qu’une taille de registre - 32 bits, par exemple - et ainsi chaque fois qu’elle fonctionne sur un caractère (en l’ajoutant, en le soustrayant ou en le comparant), une conversion implicite en int quand il est chargé dans le registre. Le compilateur prend soin de masquer et de décaler correctement le nombre après chaque opération. Ainsi, si vous ajoutez, par exemple, 2 à (caractère non signé) 254, il sera renvoyé à 0 au lieu de 256, mais dans le silicium, il jusqu'à ce que vous le sauvegardiez en mémoire.

C'est un peu académique, car le langage aurait pu spécifier un type littéral de 8 bits de toute façon, mais dans ce cas, la spécification du langage reflète plus précisément ce que le processeur fait réellement.

(x86 wonks peut noter qu'il existe par exemple un addh op natif qui ajoute les registres courts-larges en une étape, mais dans le noyau du RISC, cela se traduit en deux étapes: ajouter les nombres, puis étendre le signe, comme un add/extsh paire sur le PowerPC)

0
Crashworks