Pointeur sur l’adresse du membre de données

J’ai lu (modèle d’object Inside C ++) que l’adresse du pointeur sur le membre de données en C ++ est le décalage du membre de données plus 1?
J’essaye ceci sur VC ++ 2005 mais je n’obtiens pas les valeurs de décalage exactes.
Par exemple:

Class X{ public: int a; int b; int c; } void x(){ printf("Offsets of a=%d, b=%d, c=%d",&X::a,&X::b,&X::c); } 

Devrait imprimer des décalages de a = 1, b = 5, c = 9. Mais dans VC ++ 2005, il s’avère que a = 0, b = 4, c = 8.
Je ne suis pas capable de comprendre ce comportement.
Extrait du livre:

Msgstr “” “Cette attente, cependant, est décalée de un point – une erreur quelque peu traditionnelle pour les programmeurs C et C ++.

Le décalage physique des trois membres de coordonnée dans la structure de classe est respectivement 0, 4 et 8 si le vptr est placé à la fin ou 4, 8 et 12 si le vptr est placé au début de la classe. Cependant, la valeur renvoyée par l’adresse du membre est toujours augmentée de 1. Ainsi, les valeurs réelles sont 1, 5 et 9, etc. Le problème est de faire la distinction entre un pointeur sur aucun membre de données et un pointeur sur le premier membre de données. Considérons par exemple:

 float Point3d::*p1 = 0; float Point3d::*p2 = &Point3d::x; // oops: how to distinguish? if ( p1 == p2 ) { cout << " p1 & p2 contain the same value — "; cout << " they must address the same member!" << endl; } 

Pour distinguer p1 de p2, chaque valeur de décalage de membre réel est augmentée de 1. Par conséquent, le compilateur (et l’utilisateur) doivent ne pas oublier de soustraire 1 avant d’utiliser la valeur pour adresser un membre.

Le décalage de quelque chose est son nombre d’unités depuis le début. La première chose est au début donc son offset est zéro.

Pensez en termes de votre structure étant à l’emplacement de mémoire 100:

 100: class X { int a; 104: int b; 108: int c; 

Comme vous pouvez le constater, l’adresse de a est identique à l’adresse de la structure entière. Son décalage (ce que vous devez append à l’adresse de la structure pour obtenir l’adresse de l’article) est donc égal à 0.

Notez que la norme ISO ne spécifie pas où les éléments sont disposés en mémoire. Des octets de remplissage pour créer un alignement correct sont certainement possibles. Dans un environnement hypothétique où les entrées n’étaient que deux octets alors que leur alignement requirejs était de 256 octets, elles ne seraient pas à 0, 2 et 4 mais plutôt à 0, 256 et 512.


Et si le livre dont vous prenez l’extrait est vraiment Inside the C++ Object Model , il commence à être un peu long.

Le fait qu’il soit à partir de 96 et qu’il traite des éléments internes sous C ++ (cire lyrique sur le fait qu’il est bon de savoir où se trouve le vptr , manque le point vptr que cela fonctionne au mauvais niveau d’abstraction et que vous ne devriez jamais vous en soucier ) bit. En fait, l’introduction indique même “Explique l’ implémentation de base des fonctionnalités orientées object …” (mes italiques).

Et le fait que personne ne puisse trouver quoi que ce soit dans la norme ISO disant que ce comportement est requirejs, ainsi que le fait que ni MSVC ni gcc n’agissent de cette façon, me porte à croire que, même si cela était vrai d’une mise en œuvre lointaine dans le passé, ce n’est pas vrai (ou doit être vrai) de tous.

L’auteur a apparemment dirigé les équipes cfront 2.1 et 3 et, bien que ce livre semble présenter un intérêt historique, je ne pense pas qu’il soit pertinent pour le langage C ++ moderne (et son implémentation), du moins pour ce que j’ai lu.

Tout d’abord, la représentation interne des valeurs d’un pointeur sur un type de membre de données est un détail de mise en œuvre. Cela peut être fait de différentes manières. Vous êtes tombé sur une description d’une implémentation possible, dans laquelle le pointeur contient le décalage du membre plus 1 . Il est assez évident d’où vient ce “plus 1”: cette implémentation spécifique veut réserver la valeur zéro physique ( 0x0 ) au pointeur null , de sorte que le décalage du premier membre de données (qui pourrait facilement être 0) doit être transformé en quelque chose d’autre pour le rendre différent d’un pointeur nul. Ajouter 1 à tous ces indicateurs résout le problème.

Cependant, il convient de noter qu’il s’agit d’une approche plutôt lourde (le compilateur doit toujours soustraire 1 de la valeur physique avant d’effectuer l’access). Cette implémentation essayait apparemment très fort de s’assurer que tous les pointeurs nuls sont représentés par un motif physique à zéro bit. À vrai dire, je n’ai pas rencontré d’implémentations qui suivent cette approche dans la pratique de nos jours.

Aujourd’hui, les implémentations les plus courantes (telles que GCC ou MSVC ++) utilisent uniquement le décalage brut (sans rien y append) en tant que représentation interne du pointeur sur un membre de données. Bien entendu, le zéro physique ne fonctionnera plus pour représenter les pointeurs nuls. Ils utilisent donc une autre valeur physique pour représenter les pointeurs nuls, tels que 0xFFFF... (c’est ce que GCC et MSVC ++ utilisent).

Deuxièmement, je ne comprends pas ce que vous essayiez de dire avec votre exemple p1 et p2 . Vous avez absolument tort de supposer que les pointeurs contiendront la même valeur. Ils ne vont pas.

Si nous suivons l’approche décrite dans votre message (“offset + 1”), alors p1 recevra la valeur physique du pointeur nul (apparemment un 0x0 physique), alors que le p2 recevra la valeur physique de 0x1 (en supposant que x a le décalage 0) . 0x0 et 0x1 sont deux valeurs différentes .

Si nous suivons l’approche utilisée par les compilateurs modernes GCC et MSVC ++, p1 recevra alors la valeur physique de 0xFFFF.... (pointeur nul), tandis que p2 verra atsortingbuer un 0x0 physique. 0xFFFF... et 0x0 sont à nouveau des valeurs différentes.

PS Je viens de me rendre compte que les exemples p1 et p2 sont en réalité pas les vôtres, mais une citation d’un livre. Eh bien, le livre, encore une fois, décrit le même problème que j’ai mentionné ci-dessus – le conflit de 0 offset avec une représentation 0x0 pour le pointeur null et offre une approche viable possible pour résoudre ce conflit. Mais, encore une fois, il existe d’autres moyens de le faire et de nombreux compilateurs utilisent aujourd’hui des approches complètement différentes.

Le comportement que vous développez me semble tout à fait raisonnable. Ce qui semble faux est ce que vous lisez.

Pour compléter la réponse de AndreyT: Essayez d’exécuter ce code sur votre compilateur.

 void test() { using namespace std; int X::* pm = NULL; cout << "NULL pointer to member: " << " value = " << pm << ", raw byte value = 0x" << hex << *(unsigned int*)&pm << endl; pm = &X::a; cout << "pointer to member a: " << " value = " << pm << ", raw byte value = 0x" << hex << *(unsigned int*)&pm << endl; pm = &X::b; cout << "pointer to member b: " << " value = " << pm << ", raw byte value = 0x" << hex << *(unsigned int*)&pm << endl; } 

Sur Visual Studio 2008, je reçois:

 NULL pointer to member: value = 0, raw byte value = 0xffffffff pointer to member a: value = 1, raw byte value = 0x0 pointer to member b: value = 1, raw byte value = 0x4 

Donc, en effet, ce compilateur utilise un modèle de bits spécial pour représenter un pointeur NULL et laisse ainsi un modèle de bits 0x0 représentant un pointeur sur le premier membre d'un object.

Cela signifie également que chaque fois que le compilateur génère du code pour traduire un tel pointeur en entier ou en booléen, il doit veiller à rechercher ce modèle de bits spécial. Ainsi, quelque chose comme if(pm) ou la conversion effectuée par l'opérateur de stream << est en fait écrite par le compilateur en tant que test par rapport au modèle de bit 0xffffffff (au lieu de ce que nous aimons généralement penser que les tests de pointeur sont des tests bruts par rapport à l'adresse 0x0. ).

J’ai lu que l’adresse du pointeur sur le membre de données en C ++ est le décalage du membre de données plus 1?

Je n’ai jamais entendu cela et vos propres preuves empiriques montrent que ce n’est pas le cas. Je pense que vous avez mal compris une propriété étrange de structs & class en C ++. S’ils sont complètement vides, ils ont néanmoins une taille de 1 (pour que chaque élément d’un tableau ait une adresse unique)

9,2 / 12 $ est intéressant

Les membres de données non statiques d’une classe (non syndiquée) déclarée sans un spécificateur d’access intervenant sont alloués de sorte que les membres ultérieurs aient des adresses plus élevées dans un object de classe. L’ordre d’atsortingbution des membres de données non statiques séparés par un spécificateur d’access est non spécifié (11.1). Les exigences d’alignement de la mise en œuvre pourraient empêcher deux membres adjacents de se voir atsortingbuer l’un après l’autre. il en va de même pour les besoins en espace pour la gestion des fonctions virtuelles (10.3) et des classes de base virtuelles (10.1).

Ceci explique que ce comportement est défini par la mise en œuvre. Cependant, le fait que “a”, “b” et “c” se trouvent à des adresses croissantes est conforme à la norme.

Vous voudrez peut-être vérifier comment les objects sont stockés en mémoire en C ++? qui parle de cette question dans beaucoup plus de détails.