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

StackOverflow https://stackoverflow.com/questions/433895

  •  10-07-2019
  •  | 
  •  

Question

En C ++, sizeof ('a') == sizeof (char) == 1 . Cela a un sens intuitif, car 'a' est un littéral de caractère et sizeof (char) == 1 tel que 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 de nombreuses mentions de ce caprice, mais aucune explication de son existence.

Était-ce utile?

La solution

discussion sur le même sujet

  

"Plus précisément les promotions intégrales. Dans K & R C, c’était pratiquement (?)   impossible d'utiliser une valeur de caractère sans qu'elle soit promue d'abord en int,   rendant ainsi le caractère constant int en premier lieu éliminé cette étape.   Il y avait et il y a toujours des constantes multi-caractères telles que 'abcd' ou cependant   beaucoup rentreront dans un int. "

Autres conseils

La question initiale est "pourquoi?"

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

Dans les jours sombres du début du C, il n’y en avait pas 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 paramètre soit la taille d’un int (cela inclut tous les pointeurs) ou un double.

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

Cela rendait les choses un peu incohérentes. Ainsi, lorsque K & amp R écrivait son célèbre livre, ils indiquaient la règle selon laquelle un caractère littéral serait toujours promu en int dans une expression, pas seulement un 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 (cela 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 dans C.

C'est pourquoi ils sont différents. Evolution ...

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érez 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 comportant 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 , tandis que 'a' a le type char .

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

qui suggère qu'un caractère est composé de 8 bits, comme vous le suspectez, mais qu'un caractère littéral est un entier.

À 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 en assembleur: les 8 bits les plus bas contiendront le code de caractères, les autres bits remis à 0. Le PDP-11 avait 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 caractères dans les registres est tout à fait normale et souhaitable. Mais supposons que vous ayez besoin d'insérer "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 seulement 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 l'endurance de la CPU, l’un ou l’autre aurait besoin de passer à l’octet de poids 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 encoder la valeur sous la forme 0 'A' ou 'A' 0 - en fonction de l'endianité, et en s'assurant également qu'elle est correctement aligné (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, page 6-25 de http: //www.dmv .net / dec / pdf / macro.pdf )

Je me souviens d'avoir lu K & amp; R et d'avoir vu un extrait de code permettant de lire un caractère à la fois jusqu'à ce qu'il atteigne EOF. Etant 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. Le code avait pour effet de placer 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 é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;
}

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

  

En C, le type d'un littéral de caractère tel que 'a' est int .   Étonnamment, donner 'un' de 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   à la fois en C et en C ++ donne le même résultat.

Donc, dans la plupart des cas, cela ne devrait poser aucun problème.

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

EDIT: Pour vérifier, j'ai vérifié ma copie de Programmation d'Expert C: Deep Secrets et j'ai confirmé qu'un caractère littéral de caractère ne ne commence pas par un type < strong> int . Il est initialement de type char , mais lorsqu'il est utilisé dans une expression , il est promu en int . Ce qui suit est tiré du livre:

  

Les littéraux de caractère ont le type 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 d'une expression est   converti en un int .... Notez que   tous les float est dans une expression   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   en particulier, char et short deviennent int,   float devient double.

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 mot, prenant en charge l’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 à un int devait être élargi à int pour pouvoir être transmis à ou par 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 pour cette raison que les règles de promotion des entiers indiquent toujours que tout type de données inférieur à int est promu en int . Les implémentations 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 les premiers ordinateurs du DEC avaient une taille de mot divisible en blocs de trois octets, mais pas en quatre octets.

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 jusqu’à C ++, lorsque le type a pu déterminer la fonction à appeler, qu’il fallait le réparer.

Je ne le savais pas vraiment. Avant que les prototypes n'existent, 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.

Ceci 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 - disons 32 bits - et ainsi chaque fois que cela fonctionne sur un caractère (en l’ajoutant, en le soustrayant ou en le comparant) ) il y a 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 à l'intérieur du 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 nat natif qui ajoute les registres courts-larges en une étape, mais dans le noyau du RISC, cela se traduit en deux étapes: ajoutez les nombres, puis étendre le signe, comme une paire add / extsh sur le PowerPC)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top