En effectuant un test de base en exécutant un simple programme C++ sur un PC de bureau normal, il semble plausible de supposer que les tailles de pointeurs de tout type (y compris les pointeurs vers les fonctions) sont égales aux bits d'architecture cible?
Par exemple: dans les architectures 32 bits -> 4 octets et dans les architectures 64 bits -> 8 octets.
Cependant je me souviens avoir lu ça, ce n'est pas comme ça en général!
Je me demandais donc quelles seraient ces circonstances?
Non, il n'est pas raisonnable de supposer. Faire cette hypothèse peut provoquer des bugs.
Les tailles des pointeurs (et des types entiers) en C ou C++ sont finalement déterminées par l'implémentation C ou C++. Les implémentations C ou C++ normales sont fortement influencées par les architectures et les systèmes d'exploitation qu'elles ciblent, mais elles peuvent choisir la taille de leurs types pour des raisons autres que la vitesse d'exécution, telles que des objectifs de prise en charge d'une utilisation de mémoire plus petite, la prise en charge de code qui n'a pas été écrit dans être entièrement portable à n'importe quelle taille de type, ou prendre en charge une utilisation plus facile de grands nombres entiers.
J'ai vu un compilateur ciblé pour un système 64 bits mais fournissant des pointeurs 32 bits, dans le but de créer des programmes avec une utilisation de mémoire plus petite. (Il avait été observé que la taille des pointeurs était un facteur considérable dans la consommation de mémoire, en raison de l'utilisation de nombreuses structures avec de nombreuses connexions et références utilisant des pointeurs.) Code source écrit avec l'hypothèse que la taille du pointeur était égale au registre 64 bits la taille se briserait.
Il est raisonnable de supposer qu'en général les tailles de pointeurs de tout type (y compris les pointeurs vers les fonctions) sont égales aux bits d'architecture cible
Dépend. Si vous visez une estimation rapide de la consommation de mémoire, cela peut être suffisant.
(y compris les pointeurs vers les fonctions)
Mais voici une remarque importante. Bien que la plupart des pointeurs aient la même taille, les pointeurs de fonction peuvent différer. Il n'est pas garanti qu'un void*
pourra contenir un pointeur de fonction. Au moins, cela est vrai pour C. Je ne connais pas C++.
Je me demandais donc quelles seraient ces circonstances, le cas échéant?
Il peut y avoir des tonnes de raisons pour lesquelles cela diffère. Si l'exactitude de vos programmes dépend de cette taille, il n'est JAMAIS acceptable de faire une telle hypothèse. Vérifiez plutôt. Ça ne devrait pas être difficile du tout.
Vous pouvez utiliser cette macro pour vérifier ces choses au moment de la compilation en C:
#include <assert.h>
static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");
Lors de la compilation, cela donne un message d'erreur:
$ gcc main.c
In file included from main.c:1:
main.c:2:1: error: static assertion failed: "Pointers are assumed to be exactly 4 bytes"
static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");
^~~~~~~~~~~~~
Si vous utilisez C++, vous pouvez ignorer #include <assert.h>
car static_assert
est un mot clé en C++. (Et vous pouvez utiliser le mot clé _Static_assert
en C, mais ça a l'air moche, alors utilisez plutôt l'inclusion et la macro.)
Étant donné que ces deux lignes sont extrêmement faciles à inclure dans votre code, il n'y a AUCUNE excuse pour ne pas le faire si votre programme ne fonctionne pas correctement avec la mauvaise taille de pointeur.
Il est raisonnable de supposer qu'en général les tailles de pointeurs de tout type (y compris les pointeurs vers les fonctions) sont égales aux bits d'architecture cible?
Cela peut être raisonnable, mais ce n'est pas fiable. Donc je suppose que la réponse est "non, sauf quand vous savez déjà que la réponse est oui (et que vous ne vous inquiétez pas de la portabilité)" .
Potentiellement:
les systèmes peuvent avoir différentes tailles de registre et utiliser différentes largeurs sous-jacentes pour les données et l'adressage: il n'est pas évident de savoir ce que "bits d'architecture cible" signifient même pour un tel système, vous devez donc choisir un ABI spécifique (et une fois que vous l'avez fait, vous connaître la réponse, pour cet ABI).
les systèmes peuvent prendre en charge différents modèles de pointeurs, tels que les anciens pointeurs near
, far
et huge
; dans ce cas, vous devez savoir dans quel mode votre code est compilé (et vous connaissez la réponse, pour ce mode)
Enfin, cette hypothèse ne présente aucun avantage évident, car vous pouvez simplement utiliser sizeof(T)
directement pour tout T
qui vous intéresse.
Si vous souhaitez convertir entre des entiers et des pointeurs, utilisez intptr_t
. Si vous souhaitez stocker des entiers et des pointeurs dans le même espace, utilisez simplement un union
.
L'architecture cible "bits" indique la taille des registres. Ex. Intel 8051 est 8 bits et fonctionne sur des registres 8 bits, mais la RAM (externe) et la ROM (externe) sont accessibles avec des valeurs 16 bits.
Pour l'exactitude , vous ne pouvez rien supposer. Vous devez vérifier et être prêt à faire face à des situations étranges.
En tant que règle générale générale , il s'agit d'une valeur par défaut raisonnable hypothèse .
Ce n'est pas universellement vrai cependant. Voir le X32 ABI , par exemple, qui utilise des pointeurs 32 bits sur des architectures 64 bits pour économiser un peu de mémoire et l'empreinte du cache. Idem pour l'ILP32 ABI sur AArch64.
Donc, pour évaluer la mémoire, vous pouvez utiliser votre hypothèse et ce sera souvent le cas.
Il est raisonnable de supposer qu'en général les tailles de pointeurs de tout type (y compris les pointeurs vers les fonctions) sont égales aux bits d'architecture cible?
Si vous regardez tous les types de CPU (y compris les microcontrôleurs) actuellement produits, je dirais que non.
Des contre-exemples extrêmes seraient des architectures où deux tailles de pointeur différentes sont utilisées dans le même programme :
x86, 16 bits
Sous MS-DOS et Windows 16 bits, un programme "normal" utilisait des pointeurs 16 et 32 bits.
x86, segmenté 32 bits
Il n'y avait que quelques systèmes d'exploitation moins connus utilisant ce modèle de mémoire.
Les programmes utilisaient généralement des pointeurs 32 et 48 bits.
STM8A
Ce processeur automobile 8 bits moderne utilise des pointeurs 16 et 24 bits. Les deux dans le même programme, bien sûr.
AVR minuscule série
La RAM est adressée à l'aide de pointeurs 8 bits, Flash est adressée à l'aide de pointeurs 16 bits.
(Cependant, AVR tiny ne peut pas être programmé avec C++, pour autant que je sache.)
Ce n'est pas correct, par exemple les pointeurs DOS (16 bits) peuvent être loin (seg + ofs).
Cependant, pour les cibles habituelles (Windows, OSX, Linux, Android, iOS), c'est correct. Parce qu'ils utilisent tous le modèle de programmation plat qui repose sur la pagination.
En théorie, vous pouvez également avoir des systèmes qui n'utilisent que les 32 bits inférieurs en x64. Un exemple est un exécutable Windows lié sans LARGEADDRESSAWARE. Cependant, cela aide le programmeur à éviter les bogues lors du passage à x64. Les pointeurs sont tronqués à 32 bits, mais ils sont toujours à 64 bits.
Dans les systèmes d'exploitation x64, cette hypothèse est toujours vraie, car le mode plat est le seul valide. Le mode long dans le processeur force les entrées GDT à être à 64 bits.
On mentionne également un ABI x32, je crois qu'il est basé sur la même technologie de pagination, forçant tous les pointeurs à être mappés sur les 4 Go inférieurs. Cependant, cela doit être basé sur la même théorie que dans Windows. En x64, vous ne pouvez avoir que le mode plat.
En mode protégé 32 bits, vous pouvez avoir des pointeurs jusqu'à 48 bits. (Mode segmenté). Vous pouvez également avoir des portes d'appel. Mais, aucun système d'exploitation n'utilise ce mode.
Historiquement, sur les micro-ordinateurs et les microcontrôleurs, les pointeurs étaient souvent plus larges que les registres à usage général, de sorte que le processeur pouvait adresser suffisamment de mémoire et tenir dans le budget du transistor. La plupart des processeurs 8 bits (tels que les 8080, Z80 ou 6502) avaient des adresses 16 bits.
Aujourd'hui, une incompatibilité est plus susceptible de se produire car une application n'a pas besoin de plusieurs gigaoctets de données, donc économiser quatre octets de mémoire sur chaque pointeur est une victoire.
C et C++ fournissent tous deux size_t
, uintptr_t
et off_t
types, représentant la plus grande taille d'objet possible (qui peut être inférieure à la taille d'un pointeur si le modèle de mémoire n'est pas plat), un type intégral suffisamment large pour contenir un pointeur et un décalage de fichier (souvent plus large que le plus grand objet autorisé en mémoire), respectivement. UNE size_t
(non signé) ou ptrdiff_t
(signé) est le moyen le plus portable pour obtenir la taille native de Word. De plus, POSIX garantit que le compilateur système a un indicateur qui signifie qu'un long
peut contenir n'importe lequel de ces éléments, mais vous ne pouvez pas toujours le supposer.
En règle générale, les pointeurs seront de taille 2 sur un système 16 bits, 3 sur un système 24 bits, 4 sur un système 32 bits et 8 sur un système 64 bits. Cela dépend de l'implémentation ABI et C. AMD a longs et anciens modes, et il y a différences entre AMD64 et Intel64 pour le langage d'assemblage programmeurs mais ceux-ci sont masqués pour les langages de niveau supérieur.
Tout problème avec le code C/C++ est probablement dû à de mauvaises pratiques de programmation et à l'ignorance des avertissements du compilateur. Voir: " 20 problèmes de portage de code C++ vers la plate-forme 64 bits ".
Voir aussi: " Les pointeurs peuvent-ils être de tailles différentes? " et Réponse de LRiO :
... vous posez des questions sur C++ et ses implémentations conformes, pas sur une machine physique spécifique. Je devrais citer l'intégralité de la norme pour le prouver , mais le simple fait est qu'il ne fait aucune garantie sur le résultat de sizeof (T *) pour tout T, et (en corollaire) ne garantit pas que sizeof (T1 *) == sizeof (T2 *) pour tout T1 et T2).
Remarque: Où est réponse de JeremyP , C99 section 6.3.2.3, sous-section 8:
Un pointeur vers une fonction d'un type peut être converti en pointeur vers une fonction d'un autre type et inversement; le résultat doit être égal au pointeur d'origine. Si un pointeur converti est utilisé pour appeler une fonction dont le type n'est pas compatible avec le type pointé, le comportement n'est pas défini.
Dans GCC, vous pouvez éviter les hypothèses incorrectes en utilisant les fonctions intégrées: " Object Size Checking Built-in Functions ":
Fonction intégrée: size_t __builtin_object_size (const void * ptr, int type)
est une construction intégrée qui retourne un nombre constant d'octets de ptr à la fin de l'objet pointeur vers lequel pointe le pointeur (s'il est connu au moment de la compilation). Pour déterminer la taille des objets alloués dynamiquement, la fonction s'appuie sur les fonctions d'allocation appelées pour obtenir le stockage à déclarer avec l'attribut alloc_size (voir Attributs de fonction communs). __builtin_object_size n'évalue jamais ses arguments pour les effets secondaires. S'il y a des effets secondaires, il renvoie (size_t) -1 pour le type 0 ou 1 et (size_t) 0 pour le type 2 ou 3. S'il y a plusieurs objets ptr peut pointer vers et tous sont connus au moment de la compilation , le nombre renvoyé est le nombre maximal d'octets restants dans ces objets si le type & 2 est 0 et le minimum s'il est différent de zéro. S'il n'est pas possible de déterminer vers quels objets ptr pointe au moment de la compilation, __builtin_object_size doit retourner (size_t) -1 pour le type 0 ou 1 et (size_t) 0 pour le type 2 ou 3.