Qui décide de la taille d'un type de données ou d'une structure (selon 32 bits ou 64 bits)? Le compilateur ou le processeur? Par exemple, sizeof(int)
est de 4 octets pour un système 32 bits alors qu'il est de 8 octets pour un système 64 bits.
J'ai également lu que sizeof(int)
est de 4 octets lors de la compilation en utilisant à la fois 32 bits et 64 bits compilateur.
Supposons que mon processeur puisse exécuter à la fois des applications 32 bits et 64 bits, qui joueront un rôle principal dans la décision de la taille des données le compilateur ou processeur?
C'est finalement le compilateur. Les implémenteurs du compilateur peuvent décider d'émuler la taille entière qu'ils jugent appropriée, indépendamment de ce que le processeur gère le plus efficacement. Cela dit, la norme C (et C++) est écrite de telle sorte que l'implémenteur du compilateur est libre de choisir le moyen le plus rapide et le plus efficace. Pour de nombreux compilateurs, les implémenteurs ont choisi de conserver int sur 32 bits, bien que le processeur gère nativement les entrées 64 bits de manière très efficace.
Je pense que cela a été fait en partie pour augmenter la portabilité vers les programmes écrits lorsque les machines 32 bits étaient les plus courantes et qui s'attendaient à ce qu'un int soit 32 bits et non plus. (Il pourrait également être, comme l'utilisateur souligne user3386109 , que les données 32 bits étaient préférées car elles prennent moins d'espace et sont donc accessibles plus rapidement.)
Donc, si vous voulez vous assurer d’obtenir des ints 64 bits, vous utilisez int64_t
au lieu de int
pour déclarer votre variable. Si vous savez que votre valeur tiendra à l'intérieur de 32 bits ou que vous ne vous souciez pas de la taille, vous utilisez int
pour laisser le compilateur choisir la représentation la plus efficace.
Quant aux autres types de données tels que struct
, ils sont composés à partir des types de base tels que int
.
Ce n'est ni le CPU, ni le compilateur, ni le système d'exploitation. C'est tous les trois en même temps.
Le compilateur ne peut pas tout inventer. Il doit adhérer au bon ABI [1] fourni par le système d'exploitation. Si les structures et les appels système fournis par le système d'exploitation ont des types avec certaines tailles et exigences d'alignement, le compilateur n'est pas vraiment libre de créer sa propre réalité à moins que les développeurs du compilateur ne souhaitent réimplémenter les fonctions d'encapsuleur pour tout ce que le système d'exploitation fournit. Ensuite, l'ABI du système d'exploitation ne peut pas être entièrement constitué, il doit faire ce qui peut être raisonnablement fait sur le CPU. Et très souvent, l'ABI d'un système d'exploitation sera très similaire à d'autres ABI pour d'autres systèmes d'exploitation sur le même CPU, car il est plus facile de pouvoir simplement réutiliser le travail qu'ils ont fait (sur les compilateurs, entre autres).
Dans le cas d'ordinateurs qui prennent en charge le code 32 bits et 64 bits, le système d'exploitation doit encore travailler pour prendre en charge les programmes en cours d'exécution dans les deux modes (car le système doit fournir deux ABI différents). Certains systèmes d'exploitation ne le font pas et sur ceux-là, vous n'avez pas le choix.
[1] ABI signifie Application Binary Interface. Il s'agit d'un ensemble de règles régissant l'interaction d'un programme avec le système d'exploitation. Il définit comment un programme est stocké sur le disque pour être exécuté par le système d'exploitation, comment effectuer des appels système, comment se lier à des bibliothèques, etc. Mais pour pouvoir se lier à des bibliothèques par exemple, votre programme et la bibliothèque doivent se mettre d'accord sur la façon de faire des appels de fonction entre votre programme et la bibliothèque (et vice versa) et pour pouvoir faire des appels de fonction, le programme et la bibliothèque doivent avoir la même idée de la disposition de la pile, de l'utilisation des registres, des conventions d'appel de fonction, etc. Et pour les appels de fonction, vous devez vous mettre d'accord sur la signification des paramètres et cela inclut les tailles, l'alignement et la signature des types.
C'est strictement, à 100%, entièrement le compilateur qui décide de la valeur de sizeof (int). Ce n'est pas une combinaison du système et du compilateur. Ce n'est que le compilateur (et les spécifications du langage C/C++).
Si vous développez des applications iPad ou iPhone, vous exécutez le compilateur sur votre Mac. Le Mac et l'iPhone/iPac utilisent des processeurs différents. Rien sur votre Mac ne dit au compilateur quelle taille doit être utilisée pour int sur l'iPad.
Le concepteur de processeur détermine quels registres et instructions sont disponibles, quelles sont les règles d'alignement pour un accès efficace, la taille des adresses de mémoire, etc.
La norme C définit des exigences minimales pour les types intégrés. "char" doit être d'au moins 8 bits, "short" et "int" doit être d'au moins 16 bits, "long" doit être d'au moins 32 bits et "long long" doit être d'au moins 64 bits. Il indique également que "char" doit être équivalent à la plus petite unité de mémoire que le programme peut traiter et que l'ordre de taille des types standard doit être maintenu.
D'autres normes peuvent également avoir un impact. Par exemple, la version 2 de la "spécification Unix unique" indique que int doit être d'au moins 32 bits.
Enfin, le code existant a un impact. Le portage est déjà assez difficile, personne ne veut le rendre plus difficile que nécessaire.
Lors du portage d'un OS et d'un compilateur vers un nouveau CPU, quelqu'un doit définir ce que l'on appelle un "C ABI". Ceci définit comment le code binaire se communique, y compris.
En général, une fois et ABI est défini pour une combinaison de famille de CPU et de système d'exploitation, cela ne change pas beaucoup (parfois la taille des types plus obscurs comme les changements "longs doubles"). Le changer apporte un tas de casse pour un gain relativement faible.
De même, ceux qui portent un système d'exploitation sur une plate-forme ayant des caractéristiques similaires à une plate-forme existante choisiront généralement les mêmes tailles que sur les plates-formes précédentes sur lesquelles le système d'exploitation était porté.
Dans la pratique, les fournisseurs de systèmes d'exploitation/compilateurs choisissent généralement l'une des quelques combinaisons de tailles pour les types entiers de base.
Les processeurs 64 bits peuvent généralement exécuter des binaires 32 bits et 64 bits. Généralement, cela est géré en ayant une couche de compatibilité dans votre système d'exploitation. Ainsi, votre binaire 32 bits utilise les mêmes types de données qu'il utiliserait lors de l'exécution sur un système 32 bits, puis la couche de compatibilité traduit les appels système afin que le système d'exploitation 64 bits puisse les gérer.
Le compilateur décide de la taille des types de base et de la disposition des structures. Si une bibliothèque déclare des types, elle décidera de la façon dont ceux-ci sont définis et donc de leur taille.
Cependant, il arrive souvent que la compatibilité avec une norme existante et la nécessité de se lier à des bibliothèques existantes produites par d'autres compilateurs obligent une implémentation donnée à faire certains choix. Par exemple, la norme linguistique indique qu'un wchar_t
doit être plus large que 16 bits, et sous Linux, il est de 32 bits de large, mais il a toujours été de 16 bits sous Windows, donc les compilateurs pour Windows choisissent tous d'être compatibles avec l'API Windows au lieu du langage standard. Beaucoup de code hérité pour Linux et Windows suppose qu'un long
a exactement 32 bits de largeur, tandis que d'autres codes supposaient qu'il était suffisamment large pour contenir un horodatage en secondes ou une adresse IPv4 ou un décalage de fichier ou les bits d'un pointeur, et (après qu'un compilateur a défini int
comme 64 bits de large et long
comme 32 bits de large) la norme de langage a fait une nouvelle règle selon laquelle int
ne peut pas être plus large que long
.
En conséquence, les compilateurs grand public de ce siècle choisissent de définir int
comme 32 bits de large, mais historiquement, certains l'ont défini comme 16 bits, 18 bits, 32 bits, 64 bits et d'autres tailles. Certains compilateurs vous permettent de choisir si long
aura exactement 32 bits de largeur, comme le suppose certains codes hérités, ou aussi large qu'un pointeur, comme le suppose d'autres codes hérités.
Cela montre comment les hypothèses que vous faites aujourd'hui, comme certains types ayant toujours une largeur de 32 bits, pourraient revenir vous mordre à l'avenir. Cela est déjà arrivé deux fois aux bases de code C, lors des transitions en code 32 bits et 64 bits.
Mais que devez-vous réellement tiliser?
Le type int
est rarement utile de nos jours. Il y a généralement un autre type que vous pouvez utiliser qui offre une meilleure garantie de ce que vous obtiendrez. (Il a un avantage: les types qui ne sont pas aussi larges qu'un int
pourraient être automatiquement élargis à int
, ce qui pourrait provoquer quelques bugs vraiment étranges lorsque vous mélangez des types signés et non signés, et int
est le plus petit type garanti pour ne pas être plus court que int
.)
Si vous utilisez une API particulière, vous souhaiterez généralement utiliser le même type que lui. Il existe de nombreux types dans la bibliothèque standard à des fins spécifiques, tels que clock_t
pour les tics d'horloge et time_t
pour le temps en secondes.
Si vous voulez le type le plus rapide d'au moins 16 bits de large, c'est int_fast16_t
, et il existe d'autres types similaires. (Sauf indication contraire, tous ces types sont définis dans <stdint.h>
.) Si vous souhaitez que le plus petit type d'au moins 32 bits de large, pour regrouper le plus de données dans vos tableaux, c'est int_least32_t
. Si vous voulez le type le plus large possible, c'est intmax_t
. Si vous savez que vous voulez exactement 32 bits, et que votre compilateur a un type comme ça, c'est int32_t
Si vous voulez quelque chose de 32 bits de large sur une machine 32 bits et 64 bits de large sur une machine 64 bits, et toujours la bonne taille pour stocker un pointeur, c'est intptr_t
. Si vous voulez un bon type pour faire de l'indexation de tableaux et des calculs de pointeurs, c'est ptrdiff_t
de <stddef.h>
. (Celui-ci est dans un en-tête différent car il provient de C89, pas de C99.)
Utilisez le type que vous voulez vraiment dire!
Lorsque vous parlez du compilateur, vous avez une image claire de build|Host|target
, c'est-à-dire la machine sur laquelle vous construisez (build), la machine pour laquelle vous construisez (Host) et la machine pour laquelle GCC produira du code pour (cible), car pour la "compilation croisée", c'est très différent de " compilation native ".
À propos de la question "qui décide de la taille du type de données et de la structure", cela dépend du système cible pour lequel vous avez dit au compilateur de construire le binaire. Si la cible est de 64 bits, le compilateur traduira sizeof (long) en 8, et si la cible est une machine 32 bits, le compilateur traduira sizeof (long) en 4. Tout cela a été prédéfini par le fichier d'en-tête que vous avez utilisé pour construire votre programme. Si vous lisez votre $ MAKETOP/usr/include/stdint.h, il y a des typedefs pour définir la taille de votre type de données.
Pour éviter l'erreur créée par la différence de taille, Google coding style-Integer_Types recommande d'utiliser des types comme int16_t, uint32_t, int64_t, etc. Ceux-ci ont été définis dans <stdint.h>
.
Ci-dessus ne sont que les "anciennes données simples", telles que l'int. Si vous parlez d'une structure, il y a une autre histoire, car la taille d'une structure dépend de alignement de l'emballage , l'alignement des limites pour chaque champ de la structure, ce qui aura un impact sur la taille de la structure .
C'est le compilateur, et plus précisément son composant générateur de code.
Bien sûr, le compilateur est sensible à l'architecture et fait des choix qui lui correspondent.
Dans certains cas, le travail est effectué en deux passes, une au moment de la compilation par un générateur de code intermédiaire, puis une seconde au moment de l'exécution par un compilateur juste à temps. Mais c'est toujours un compilateur.