Toute variable en C a un type, choisi à la déclaration et figé pour sa durée de vie. Le type décide de trois choses : combien d'octets la variable occupe, comment ces octets sont interprétés, et quelles opérations ont un sens sur elle.
La liste des types du C est courte. La complexité est dans les modificateurs.
Les quatre types de base
charcontient un octet. Il sert aux caractères isolés ('A'), aux octets d'une chaîne, et parfois de tout petit entier.intest le type entier de travail. Compteurs de boucle, tailles, identifiants.floatetdoublecontiennent des nombres réels.doublea environ deux fois plus de précision.voidest en un sens le quatrième type, mais il signifie « aucune valeur » - vous ne pouvez pas déclarer une variablevoid, seulement l'utiliser comme type de retour de fonction, comme liste de paramètres vide, ou comme pointeur sans type.
Notez le suffixe f sur 3.14f. Sans lui, le littéral est un double, et l'affecter à un float perd de la précision silencieusement. Certains compilateurs avertissent.
Modificateurs : short, long, unsigned
Les types entiers de base viennent avec des modificateurs qui changent la taille ou le signe.
Chaque type entier a son propre spécificateur de format : %hd pour short, %d pour int, %ld pour long, %lld pour long long, %u pour unsigned. Utiliser le mauvais est un comportement indéfini, pas une erreur d'arrondi.
unsigned supprime le bit de signe et double la plage positive. Un unsigned int contient de 0 à environ 4,3 milliards au lieu de -2,1 à +2,1 milliards. C'est le bon choix pour ce qui ne peut réellement pas être négatif - tailles, comptes d'octets, motifs de bits - et un piège pour tout ce qui pourrait être soustrait en dessous de zéro.
signed est la valeur par défaut pour int, short, long et long long, vous ne l'écrivez donc presque jamais. Le seul endroit où cela compte est char : le fait qu'un char simple soit signé ou non est défini par l'implémentation, alors écrivez signed char ou unsigned char quand le signe compte.
Tailles : ce que vous obtenez réellement
La norme C spécifie des minimums, pas des tailles exactes. Sur toute machine moderne 64 bits sous Linux, macOS ou Windows, vous verrez ceci :
| Type | Taille typique | Plage typique |
|---|---|---|
char | 1 octet | -128 à 127 (ou 0 à 255) |
short | 2 octets | -32 768 à 32 767 |
int | 4 octets | -2 147 483 648 à 2 147 483 647 |
long | 8 octets (4 sous Windows) | environ ±9,2 trillions |
long long | 8 octets | environ ±9,2 trillions |
float | 4 octets | ~7 chiffres significatifs |
double | 8 octets | ~15 chiffres significatifs |
long double | 16 octets (variable) | plus que double |
La ligne long est celle qui piège : 8 octets sous Linux et macOS, et 4 octets sous Windows 64 bits. Du code qui suppose que long peut contenir une valeur 64 bits n'est pas portable. Utilisez long long, ou les types de largeur exacte de stdint.h (int32_t, uint64_t) quand la taille fait partie de l'exigence.
sizeof : demandez au compilateur
Ne devinez jamais une taille - mesurez-la :
sizeof est un opérateur, pas une fonction, et il est évalué à la compilation. Il produit une valeur de type size_t, qui s'affiche avec %zu.
sizeof(char) vaut exactement 1, c'est garanti - c'est la définition d'un octet en C. Tout le reste est mesuré par rapport à lui.
limits.h et float.h
Les plages exactes pour votre compilateur sont disponibles sous forme de constantes nommées :
Ce sont les valeurs auxquelles se comparer quand vous devez savoir si une opération va déborder. Vérifier if (a > INT_MAX - b) avant de calculer a + b est la façon de détecter un débordement avant qu'il n'arrive - ce qui compte, car on ne peut pas détecter un débordement signé après coup.
Le débordement d'entier
Ce qui se passe quand une valeur dépasse la plage de son type dépend entièrement du signe.
Le débordement non signé est défini : la valeur boucle modulo 2^N.
Ce second cas est une vraie source de bugs. Une boucle comme for (unsigned i = n - 1; i >= 0; i--) ne se termine jamais, car une valeur non signée est toujours >= 0.
Le débordement signé est un comportement indéfini. Pas « il boucle » - indéfini. Le compilateur est autorisé à supposer que cela n'arrive jamais et à optimiser sur cette base, ce qui signifie qu'une vérification de débordement écrite après coup peut être supprimée :
int sum = a + b;
if (sum < a) { /* le compilateur peut supprimer entierement ceci */ }
Vérifiez avant, en utilisant les limites :
if (b > 0 && a > INT_MAX - b) {
/* a + b deborderait - gerez-le */
}
La précision en virgule flottante
float et double stockent les nombres en binaire, et la plupart des fractions décimales n'ont pas de forme binaire exacte - de la même façon que 1/3 n'a pas de forme décimale exacte.
La règle qui en découle : ne comparez jamais des valeurs flottantes avec ==. Comparez plutôt la différence absolue à une petite tolérance.
Et n'utilisez jamais de flottant pour de l'argent. Stockez les centimes en entier ; un long long de centimes est exact là où un double d'euros ne l'est pas.
Choisir un type
Une courte liste de décision qui couvre la plupart du code :
- Nombres entiers :
intsauf raison contraire. C'est le type que le processeur traite le plus efficacement et celui autour duquel toutes les règles arithmétiques sont construites. - Tout ce qui dépasse 2 milliards :
long long, ouint64_tdestdint.h. - Tailles, longueurs, indices de tableaux issus de
sizeofoustrlen:size_t. Il est non signé et garanti assez grand pour n'importe quel objet. - Décimaux :
double. N'utilisezfloatque pour diviser la mémoire par deux dans de grands tableaux ou sur du matériel embarqué sans unité double précision. - Caractères isolés et octets bruts :
charpour le texte,unsigned charpour les données binaires. - Vrai/faux :
booldestdbool.h- voir les booléens en C. - Largeurs de bits exactes (formats de fichiers, protocoles réseau, registres matériels) :
stdint.h-uint8_t,int16_t,uint32_t, et ainsi de suite.
Mélanger les types
Quand vous combinez deux types différents dans une expression, le C les convertit dans votre dos avant de faire l'arithmétique. C'est généralement utile et parfois désastreux :
Le premier cas est une division entière : les deux opérandes sont des int, donc le résultat est un int et la fraction est jetée. Le troisième est pire - comparer une valeur signée avec une non signée convertit la signée en non signée, transformant -1 en un nombre positif énorme.
Ces règles de conversion, et comment en prendre le contrôle avec des casts explicites, font l'objet de transtypage en C.
Questions fréquentes
Quels sont les types de données de base en C ?
Quatre types de base : char pour les caractères isolés et les octets, int pour les nombres entiers, float et double pour les décimaux. Des modificateurs changent leur taille et leur signe - short, long, long long, signed et unsigned - produisant l'ensemble complet.
Combien d'octets fait un int en C ?
Presque toujours 4 octets (32 bits) sur les systèmes de bureau et serveur modernes, donnant une plage d'environ -2,1 milliards à 2,1 milliards. La norme ne garantit que 2 octets au minimum, et les microcontrôleurs 16 bits en utilisent bien 2. Utilisez sizeof(int) si vous devez en être certain.
Quelle est la différence entre float et double en C ?
float fait 4 octets avec environ 7 chiffres décimaux significatifs ; double fait 8 octets avec environ 15. Le double est le type par défaut des littéraux flottants et des fonctions mathématiques, et sauf si vous stockez des millions de valeurs ou visez une puce embarquée, double est le bon choix.
Que se passe-t-il quand un int déborde en C ?
Pour un int signé, le débordement est un comportement indéfini - le compilateur peut boucler, saturer ou supprimer entièrement la vérification. Pour un int non signé, c'est parfaitement défini : la valeur boucle modulo 2^N, donc UINT_MAX + 1 vaut 0. Ne comptez jamais sur le débordement signé.