1.2. Les types prédéfinis du C/C++
Le C, et encore plus le C++, est un langage typé. Cela signifie que chaque
entité manipulée dans les programmes doit disposer d'un type de donnée grâce auquel le compilateur
pourra vérifier la validité des opérations qu'on lui appliquera. La prise en compte du type des données
peut apparaître comme une contrainte pour le programmeur, mais en réalité il s'agit surtout d'une aide
à la détection des erreurs.
Il existe plusieurs types prédéfinis. Ce sont :
le type vide : void. Ce type est utilisé
pour spécifier le fait qu'il n'y a pas de type. Cela a une utilité pour faire des procédures (fonctions
ne renvoyant rien) et les pointeurs sur des données non typées (voir plus loin) ;
les booléens : bool, qui peuvent prendre
les valeurs true et false (en C++ uniquement, ils n'existent
pas en C) ;
les caractères : char ;
les caractères longs : wchar_t (ce n'est un type
de base que pour le langage C++, mais il est également défini dans la bibliothèque standard C et
est donc utilisable malgré tout en C) ;
les entiers : int ;
les réels : float ;
les réels en double précision : double ;
les tableaux à une dimension, dont les indices sont spécifiés
par des crochets ('[' et ']'). Pour les tableaux de dimension
supérieure ou égale à 2, on utilisera des tableaux de tableaux ;
les structures, unions et énumérations
(voir plus loin).
Les types entiers (int) peuvent être caractérisés d'un des mots clés
long ou short. Ces mots clés permettent de modifier la taille du type,
c'est-à-dire la plage de valeurs qu'ils peuvent couvrir. De même, les réels en double précision peuvent être
qualifiés du mot clé long, ce qui augmente leur plage de valeurs. On ne peut pas utiliser
le mot clé short avec les double. On dispose donc de types additionnels :
les entiers longs : long int, ou long
(int est facultatif) ;
les entiers courts : short int,
ou short ;
les réels en quadruple précision : long double.
Note : Attention ! Il n'y a pas de type de base permettant de manipuler
les chaînes de caractères. En C/C++, les chaînes de caractères sont en réalité des tableaux de caractères.
Vous trouverez plus loin pour de plus amples informations sur les chaînes de caractères et les tableaux.
La taille des types n'est spécifiée dans aucune norme. La seule chose qui est
indiquée dans la norme C++, c'est que le plus petit type est le type char. Les tailles
des autres types sont donc des multiples de celle du type char. De plus, les inégalités
suivantes sont toujours vérifiées :
char ≤ short int ≤ int ≤ long int
float ≤ double ≤ long double
où l'opérateur «
≤ » signifie ici « a une plage de valeur plus
petite ou égale que ». Cela dit, les tailles des types sont généralement les mêmes pour tous
les environnements de développement. Le type
char est généralement codé sur un octet (8 bits),
le type
short int sur deux octets et le type
long int sur quatre octets.
Le type
int est celui qui permet de stocker les entiers au format natif du processeur utilisé.
Il est donc codé sur deux octets sur les machines 16 bits et sur quatre octets sur les machines 32 bits.
Enfin, la taille des caractères de type
wchar_t n'est pas spécifiée et dépend de
l'environnement de développement utilisé. Ils sont généralement codés sur deux ou sur quatre octets
suivant la représentation utilisée pour les caractères larges.
Note : Remarquez que, d'après ce qui précède, le type int devrait être codé
sur 64 bits sur les machines 64 bits. Le type long int devant lui être supérieur, il doit
également être codé sur 64 bits ou plus. Le type short int peut alors être sur 16 ou
sur 32 bits. Il n'existe donc pas, selon la norme, de type permettant de manipuler les valeurs 16 bits
sur les machines 64 bits si le type short int est codé sur 32 bits, ou, inversement,
de type permettant de manipuler les valeurs 32 bits si le type short int est codé
sur 16 bits.
Afin de résoudre ces problèmes, la plupart des compilateurs brisent la règle selon
laquelle le type int est le type des entiers natifs du processeur, et fixent sa taille
à 32 bits quelle que soit l'architecture utilisée. Ainsi, le type short est toujours
codé sur 16 bits, le type int sur 32 bits et le type long sur 32 ou 64 bits
selon que l'architecture de la machine est 32 ou 64 bits. Autrement dit, le type qui représente
les entiers nativement n'est plus le type int, mais le type long. Cela
ne change pas les programmes 32 bits, puisque ces deux types sont identiques dans ce cas.
Les programmes destinés aux machines 64 bits pourront quant à eux être optimisés en utilisant
le type long à chaque fois que l'on voudra utiliser le type de données natif
de la machine cible. Les programmes 16 bits en revanchent ne sont en revanche plus compatibles
avec ces règles, mais la plupart des compilateurs actuels ne permettent plus de compiler
des programmes 16 bits de toutes manières.
Les types char, wchar_t et int peuvent
être signés ou non. Un nombre signé peut être négatif, pas un nombre non signé. Lorsqu'un nombre est signé,
la valeur absolue du plus grand nombre représentable est plus petite. Par défaut, les nombres entiers sont
signés. Le signe des types char et wchar_t dépend du compilateur utilisé, il est
donc préférable de spécifier systématiquement si ces types sont signés ou non lorsqu'on les utilise
en tant que type entier. Pour préciser qu'un nombre n'est pas signé, il faut utiliser le mot clé
unsigned. Pour préciser qu'un nombre est signé, on peut utiliser le mot clé signed.
Ces mots clés peuvent être intervertis librement avec les mots clés long et short
pour les types entiers.
Exemple 1-3. Types signés et non signés
unsigned char
signed char
unsigned wchar_t
signed wchar_t
unsigned int
signed int
unsigned long int
long unsigned int
Note : Le C++ (et le C++ uniquement) considère les types char et
wchar_t comme les types de base des caractères. Le langage C++ distingue donc les versions
signées et non signées de ces types de la version dont le signe n'est pas spécifié, puisque les caractères
n'ont pas de notion de signe associée. Cela signifie que les compilateurs C++ traitent les types
char, unsigned char et signed char comme des types différents,
et il en est de même pour les types wchar_t, signed wchar_t et
unsigned wchar_t. Cette distinction n'a pas lieu d'être au niveau des plages de valeurs
si l'on connaît le signe du type utilisé en interne pour représenter les types char et
wchar_t, mais elle est très importante dans la détermination
de la signature des fonctions, en particulier au niveau du mécanisme
de surcharge des fonctions. Les notions de signature et de surcharge
des fonctions seront détaillées plus loin dans ce cours.
Les valeurs accessibles avec les nombres signés ne sont pas les mêmes que celles
accessibles avec les nombres non signés. En effet, un bit est utilisé pour le signe dans les nombres
signés. Par exemple, si le type char est codé sur 8 bits, on peut coder les nombres
allant de 0 à 255 avec ce type en non signé (il y a 8 chiffres binaires, chacun peut valoir 0 ou 1,
on a donc 2 puissance 8 combinaisons possibles, ce qui fait 256). En signé, les valeurs s'étendent
de -128 à 127 (un des chiffres binaires est utilisé pour le signe, il en reste 7 pour coder le nombre,
donc il reste 128 possibilités dans les positifs comme dans les négatifs. 0 est considéré comme positif.
En tout, il y a autant de possibilités.).
De même, si le type int est codé sur 16 bits (cas des machines
16 bits), les valeurs accessibles vont de -32768 à 32767 ou de 0 à 65535 si l'entier n'est pas
signé. C'est le cas sur les PC en mode réel (c'est-à-dire sous DOS) et sous Windows 3.x. Sur les machines
fonctionnant en 32 bits, le type int est stocké sur 32 bits : l'espace des valeurs
disponibles est donc 65536 fois plus large. C'est le cas sur les PC en mode protégé 32 bits (Windows 9x
ou NT, DOS Extender, Linux) et sur les Macintosh. Sur les machines 64 bits, le type int
devrait être 64 bits (DEC Alpha et la plupart des machines UNIX par exemple).
Enfin, le type float est généralement codé sur 4 octets, et les types
double et long double sont souvent identiques et codés sur 8 octets.
Note : On constate donc que la portabilité des types de base est très aléatoire.
Cela signifie qu'il faut faire extrêmement attention dans le choix des types si l'on veut faire du
code portable (c'est-à-dire qui compilera et fonctionnera sans modification du programme sur tous
les ordinateurs). Il est dans ce cas nécessaire d'utiliser des types de données qui donnent les mêmes
intervalles de valeurs sur tous les ordinateurs. La norme ISO C99 impose de définir des types portables
afin de régler ces problèmes sur toutes les architectures existantes. Ces types sont définis
dans le fichier d'en-tête stdint.h. Il s'agit des types
int8_t, int16_t, int32_t et int64_t,
et de leurs versions non signées uint8_t, uint16_t, uint32_t
et uint64_t. La taille de ces types en bits est indiquée dans leur nom et leur utilisation
ne devrait pas poser de problème.
De la même manière, deux représentations d'un même type peuvent être différentes en mémoire
sur deux machines d'architectures différentes, même à taille égale en nombre de bits. Le problème
le plus courant est l'ordre de stockage des octets en mémoire pour les types qui sont
stockés sur plus d'un octet (c'est-à-dire quasiment tous). Cela a une importance capitale lorsque
des données doivent être échangées entre des machines d'architectures a priori différentes, par exemple
dans le cadre d'une communication réseau, ou lors de la définition des formats de fichiers. Une solution
simple est de toujours d'échanger les données au format texte, ou de choisir un mode de représentation
de référence. Les bibliothèques réseau disposent généralement des méthodes permettant de convertir
les données vers un format commun d'échange de données par un réseau et pourront par exemple être
utilisées.