À propos des tailles

Article original : About sizes | Belay the C++ (belaycpp.com)
Traductrice : Chloé Lourseyre

Si j’étais venue ce matin avec une interro surprise sur les tailles des types fondamentaux en C++, beaucoup d’entre nous l’aurait sans doute échoué (moi y compris). La raison à cela : les tailles en C++ sont compliquées.

La taille de chaque type fondamental n’est pas fixée, elle dépend toujours de l’architecture (elle est « implementation-defined« ).

Pourtant, le standard impose des contraintes à ces tailles. Ces contraintes peuvent prendre deux formes différentes :

  • Une comparaison des sizeof des types
  • Le nombre minimum de bits qui composent un type

Qu’est-ce que sizeof() ?

Une des idées reçues les plus largement propagées (même si inoffensive) est celle selon laquelle un octet est composé de 8 bits.

Même si la plupart du temps c’est vrai en pratique, c’est techniquement faux.

Un byte (anglais de octet1) est en réalité défini par la taille d’un char. Bien qu’un char fasse toujours au moins 8 bits, il peut être plus grand. Dans tous les cas, on part de la définition qu’un byte est la taille d’un char.

En C++, toutes les tailles sont des multiples de la taille d’un char. Ainsi, la fonction sizeof(N) renvoie le nombre de bytes qui composent le type N.

De ce fait, si sizeof(int) vaut quatre dans une architecture donnée, cela veut dire qu’il vaut la taille de 4 char, donc au moins 32 bits. Il peut cependant être plus grand. Si un char mesure 32 bits, alors un int fera, dans ce contexte, 128 bits.

La véritable taille d’un byte est enregistrée dans la constante CHAR_BIT.

1. À partir de là et jusqu’à la fin de l’article, j’utiliserai le terme anglais byte à la place de l’équivalent français octet, car ce dernier est, de fait, étymologiquement inexact.

Résumé des tailles en C++

Voici l’intégralité des contraintes de taille sur les types fondamentaux en C++ :

  • 1 ≡ sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long)
  • 1 ≤ sizeof(bool) ≤ sizeof(long)
  • sizeof(char) ≤ sizeof(wchar_t) ≤ sizeof(long)
  • sizeof(float) ≤ sizeof(double) ≤ sizeof(long double)
  • sizeof(N) ≡ sizeof(unsigned N) ≡ sizeof(signed N)
  • Un char fait au moins 8 bits
  • Un short fait au moins 16 bits
  • Un long fait au moins 32 bits

… et c’est tout.

Fait amusant : selon cette définition, il est techniquement valide d’avoir une architecture où tous les types fondamentaux font 32 bits.

Deux mots de sagesse

Puisque les tailles des types fondamentaux dépendent entièrement de l’architecture, il peut être parfois complexe d’écrire du code fiable.

#include <limits>

L’include limits de la librairie standard contient les bornes supérieures et inférieures de tous les types fondamentaux. Il vous permet en plus de savoir si un type est signé ou pas.

Exemple :

#include <limits>
#include <iostream>
 
int main()
{
    std::cout << "largest double == " << std::numeric_limits<double>::max() << std::endl;
    std::cout << "char is signed == " << std::numeric_limits<char>::is_signed << std::endl;
}

Plus d’informations ici : std::numeric_limits – cppreference.com.

Rappel : l’overflow d’entier signé est un comportement indéfini. Utiliser les limites vous permettra d’éviter cela.

#include <cstdint>

Parfois on veut utiliser directement des types de taille définie. Quand on écrit une classe de sérialisation, quand on travaille sur des systèmes à mémoire très limitée ou quand on veut que le code soit compatible cross-plateforme, on veut pouvoir utiliser des types qui ont une longueur (en terme de bits) prédéfinie.

C’est possible avec la librairie cstdint qui contient des types de taille fixe.

En voici quelques-uns:

int8_t
int16_t
int32_t
int64_t
Signed integer type with width of exactly 8, 16, 32 and 64 bits respectively
with no padding bits and using 2’s complement for negative values
(provided only if the implementation directly supports the type)
int_least8_t
int_least16_t
int_least32_t
int_least64_t
Smallest signed integer type with width of at least 8, 16, 32 and 64 bits respectively
intmax_tMaximum-width signed integer type
uint8_t
uint16_t
uint32_t
uint64_t
Unsigned integer type with width of exactly 8, 16, 32 and 64 bits respectively
(provided only if the implementation directly supports the type)
uint_least8_t
uint_least16_t
uint_least32_t
uint_least64_t
smallest unsigned integer type with width of at least 8, 16, 32 and 64 bits respectively
uintmax_tmaximum-width unsigned integer type

Plus d’informations ici : Fixed width integer types (since C++11) – cppreference.com.

En conclusion

Si vous voulez en lire plus à propos des tailles de types, je vous renvoie à la section §6.2.8 de l’ouvrage The C++ Langage (de Bjarne Stroustrup). Plus largement, vous pouvez vous documenter à propos des types et déclaration dans toute la section §6 du livre.

Vous pouvez aussi aller voir Fundamental types – cppreference.com si vous préférez la documentation en ligne.

Merci de votre attention et à la semaine prochaine !

Article original : About sizes | Belay the C++ (belaycpp.com)
Traductrice : Chloé Lourseyre

Laisser un commentaire