52 votes

Pourquoi ne pouvez-vous pas utiliser offsetof sur des structures non-POD en C ++?

Je faisais des recherches sur la façon d'obtenir le décalage mémoire d'un membre d'une classe en C++ et suis tombé sur ceci sur wikipedia:

Dans le code C++, vous ne pouvez pas utiliser offsetof pour accéder aux membres de structures ou classes qui ne sont pas en bon Vieux Structures de Données.

Je l'ai essayé et il semble bien fonctionner.

class Foo
{
private:
    int z;
    int func() {cout << "this is just filler" << endl; return 0;}

public: 
    int x;
    int y;
    Foo* f;

    bool returnTrue() { return false; }
};

int main()
{
    cout << offsetof(Foo, x)  << " " << offsetof(Foo, y) << " " << offsetof(Foo, f);
    return 0;
}

J'ai eu quelques mises en garde, mais il a compilé et lorsqu'il est exécuté, il a donné des résultats intéressants:

Laptop:test alex$ ./test
4 8 12

Je pense que je suis en train de l'incompréhension de ce qu'un POD structure de données, ou je suis absent quelque autre morceau du puzzle. Je ne vois pas quel est le problème.

48voto

Steve Jessop Points 166970

Bluehorn réponse est bonne, mais pour moi ça n'explique pas la raison du problème en termes plus simples. La façon dont je le comprends, il est comme suit:

Si NonPOD est un non-POD classe, puis quand vous le faites:

NonPOD np;
np.field;

le compilateur n'a pas nécessairement accès du terrain en y ajoutant un peu décalée par rapport à la base de pointeur et de déférence. Pour un module de classe, la Norme C++ contraint à le faire(ou quelque chose d'équivalent), mais pour un non-POD classe, il ne le fait pas. Le compilateur peut, au lieu de lire un pointeur en dehors de l'objet, d'ajouter un décalage à qui la valeur à donner à l'emplacement de stockage du champ, puis de déréférencement. C'est un mécanisme commun avec l'héritage virtuel si le champ est un membre d'une base virtuelle de NonPOD. Mais elle n'est pas limitée à ce cas. Le compilateur peut faire à peu près tout ce qu'il aime. Il pourrait un appel masqué généré par le compilateur fonction membre virtuelle si elle veut.

Dans les cas complexes, il n'est évidemment pas possible de représenter l'emplacement du champ comme un nombre entier de décalage. Donc, offsetof n'est pas valide sur la non-POD classes.

Dans le cas où votre compilateur se trouve juste à stocker l'objet dans une façon simple (tels que l'héritage simple, et normalement, même les non-virtuel de l'héritage multiple, et, normalement, les champs définis dans la classe que vous faites référence à l'objet plutôt que par rapport à certains de la classe de base), alors il sera juste pour arriver à travailler. Il y a probablement des cas qui vient d'arriver à travailler sur chaque compilateur, il est. Ce n'est pas valide.

Annexe: comment l'héritage virtuel de travail?

Avec l'héritage simple, si B est Un dérivé de la mise en oeuvre habituelle est qu'un pointeur vers B est simplement un pointeur vers Un, avec B données supplémentaires coincé sur la fin:

A* ---> field of A  <--- B*
        field of A
        field of B

Avec un simple héritage multiple, en général, vous supposer que B est une classe de base (appeler 'em A1 et A2) sont disposées dans un ordre particulier à B. Mais le même truc avec les pointeurs ne peuvent pas travailler:

A1* ---> field of A1
         field of A1
A2* ---> field of A2
         field of A2

A1 et A2 "sais" rien sur le fait qu'ils sont à la fois des classes de base de B. Donc, si vous lancez un B* * * * A1*, il est à point pour les domaines de l'A1, et si vous le lancez à A2* il est à point pour les domaines de l'A2. Le pointeur à l'opérateur de conversion s'applique un décalage. Donc, vous pourriez vous retrouver avec ceci:

A1* ---> field of A1 <---- B*
         field of A1
A2* ---> field of A2
         field of A2
         field of B
         field of B

Puis, jetant un B* * * * A1* ne change pas la valeur du pointeur, mais un moulage A2* ajoute sizeof(A1) octets. C'est "l'autre" raison pour laquelle, en l'absence d'un destructeur virtuel, la suppression de B par l'intermédiaire d'un pointeur vers A2 va mal. Il n'est pas simplement de ne pas appeler le destructeur de B et A1, il n'a même pas libre à la bonne adresse.

De toute façon, B "sait" où l'ensemble de ses classes de base sont, ils sont toujours stockés dans le même décalages. Donc, dans cet arrangement offsetof fonctionne encore. La norme n'exige pas des implémentations de faire de l'héritage multiple de cette façon, mais ils le font souvent (ou quelque chose comme ça). Donc offsetof peut fonctionner dans ce cas sur votre mise en œuvre, mais il n'est pas garanti.

Maintenant, ce sujet de l'héritage virtuel? Supposons que B1 et B2 ont tous les deux Un virtuel de base. Cela les rend unique héritage de classes, de sorte que vous pourriez penser que la première astuce ne fonctionne de nouveau:

A* ---> field of A   <--- B1* A* ---> field of A   <--- B2* 
        field of A                    field of A
        field of B1                   field of B2

Mais s'accrocher. Ce qui se passe quand C dérive (non-pratiquement, pour des raisons de simplicité) de B1 et B2? C ne doit contenir que 1 exemplaire les domaines de l'A. Ces champs peut pas précéder immédiatement le champs de B1, et aussi précéder immédiatement le champs de B2. Nous sommes en difficulté.

Donc, ce que les implémentations pourrait faire à la place est de:

// an instance of B1 looks like this, and B2 similar
A* --->  field of A
         field of A
B1* ---> pointer to A 
         field of B1

Bien que je l'ai indiqué B1* le pointage de la première partie de l'objet après Un sous-objet, je pense (sans prendre la peine de vérifier), de l'adresse ne sera pas là, ça va être le début de A. C'est juste que contrairement à l'héritage simple, les décalages entre l'adresse du pointeur, et l'adresse que j'ai indiqué dans le diagramme, sera de ne jamais être utilisé à moins que le compilateur est certain du type dynamique de l'objet. Au lieu de cela, il sera toujours passer par la méta-information pour atteindre Un correctement. Donc, mes schémas point là, depuis ce décalage sera toujours appliquée pour les utilisations qui nous intéresse.

Le "pointeur" vers Un pourrait être un pointeur ou un décalage, il n'a pas vraiment d'importance. Dans une instance de B1, créé comme un B1, c'points de (char*)this - sizeof(A), et même dans une instance de B2. Mais si nous créons un C, il peut ressembler à ceci:

A* --->  field of A
         field of A
B1* ---> pointer to A    // points to (char*)(this) - sizeof(A) as before
         field of B1
B2* ---> pointer to A    // points to (char*)(this) - sizeof(A) - sizeof(B1)
         field of B2
C* ----> pointer to A    // points to (char*)(this) - sizeof(A) - sizeof(B1) - sizeof(B2)
         field of C
         field of C

Donc, pour accéder à un champ d'Une aide d'un pointeur ou d'une référence à B2 exige plus que juste l'application d'un décalage. Il faut lire le "pointeur vers Un" champ de B2, suivez-le, et alors seulement d'appliquer un décalage, car en fonction de la classe B2 est une base de, ce pointeur va avoir des valeurs différentes. Il n'y a pas une telle chose comme offsetof(B2,field of A): il ne peut pas l'être. offsetof sera jamais travailler avec l'héritage virtuel, sur toute mise en œuvre.

37voto

Bluehorn Points 1016

Réponse courte: offsetof est une fonctionnalité qui n'est que dans la norme C++ pour héritage C compatibilité. Par conséquent, il est essentiellement limité à des choses que peut être fait en C. en C++ prend en charge uniquement ce qui doit l'être pour les C de compatibilité.

Comme offsetof est fondamentalement un hack (mis en œuvre en tant que macro), qui s'appuie sur la mémoire simple-modèle à l'appui C, il faudrait beaucoup de liberté loin de compilateur C++ réalisateurs comment organiser la classe de l'instance de mise en page.

L'effet est que offsetof souvent de travail (en fonction du code source et du compilateur utilisé) en C++, même où pas soutenu par la norme, sauf s'il ne le fait pas. Donc, vous devriez être très prudent avec offsetof utilisation en C++, surtout depuis que je ne connais pas un seul compilateur qui génère un avertissement pour non-POD utiliser...

Edit: Comme vous l'avez demandé, par exemple, les éléments suivants pourraient clarifier le problème:

#include <iostream>
using namespace std;

struct A { int a; };
struct B : public virtual A   { int b; };
struct C : public virtual A   { int c; };
struct D : public B, public C { int d; };

#define offset_d(i,f)    (long(&(i)->f) - long(i))
#define offset_s(t,f)    offset_d((t*)1000, f)

#define dyn(inst,field) {\
    cout << "Dynamic offset of " #field " in " #inst ": "; \
    cout << offset_d(&i##inst, field) << endl; }

#define stat(type,field) {\
    cout << "Static offset of " #field " in " #type ": "; \
    cout.flush(); \
    cout << offset_s(type, field) << endl; }

int main() {
    A iA; B iB; C iC; D iD;
    dyn(A, a); dyn(B, a); dyn(C, a); dyn(D, a);
    stat(A, a); stat(B, a); stat(C, a); stat(D, a);
    return 0;
}

Ce crash lorsque vous essayez de localiser le champ a à l'intérieur de type B de manière statique, alors qu'il travaille lorsqu'une instance est disponible. C'est à cause de l'héritage virtuel, où l'emplacement de la classe de base est stockée dans une table de recherche.

Bien que ce soit un exemple artificiel, une mise en œuvre pourrait utiliser une table aussi à trouver le public, privé et protégé sections d'une instance de classe. Ou faire de la recherche totalement dynamique (utilisation d'une table de hachage pour les champs), etc.

Le standard laisse toutes les possibilités ouvertes par la limitation de l'offsetof de POD (OIE: pas moyen d'utiliser une table de hachage pour POD structures... :)

Juste une autre remarque: j'avais ré-écrire offsetof (ici: offset_s) pour cet exemple que GCC fait d'erreur quand je l'appelle offsetof pour un champ d'une classe de base virtuelle.

5voto

AProgrammer Points 31212

En général, lorsque vous demandez "pourquoi est quelque chose d'indéfini", la réponse est "parce que la norme dit". Généralement, le rationnel est le long de l'une ou plusieurs des raisons comme:

  • il est difficile de détecter de manière statique auquel cas vous êtes.

  • des cas limites sont difficiles à définir et personne n'a eu la douleur de la définition de cas particuliers;

  • son utilisation est principalement couvert par d'autres fonctions;

  • des pratiques existantes à l'époque de la normalisation varié et la rupture de mise en œuvre existantes et des programmes selon eux a été jugé plus dangereux que la normalisation.

Retour à offsetof, la deuxième raison est probablement un dominant. Si vous regardez C++0X, où la norme a été précédemment à l'aide d'un POD, il est maintenant à l'aide de "modèle standard", "mise en page compatible", "POD", permettant aux plus raffinés cas. Et offsetof doit maintenant "modèle standard" des classes, qui sont les cas où le comité ne voulais pas forcer une mise en page.

Vous avez également à considérer l'usage commun de la offsetof(), qui est d'obtenir la valeur d'un champ lorsque vous avez un void* pointeur vers l'objet. L'héritage Multiple -- virtuel ou pas -- est problématique pour cette utilisation.

0voto

Roopesh Majeti Points 338

Pour la définition de la GOUSSE de structure de données,ici vous allez avec l'explication [ déjà posté dans un autre post dans le Débordement de la Pile ]

http://stackoverflow.com/questions/146452/what-are-pod-types-in-c/146589

Maintenant, pour en venir à votre code, il fonctionne très bien comme prévu. C'est parce que, vous essayez de trouver le offsetof(), pour le public et les membres de votre classe, qui est valide.

S'il vous plaît laissez-moi savoir, la bonne question, si mon point de vue ci-dessus, n'est pas à clarifier vos doutes.

0voto

djna Points 34761

Tu es encore un peu POD. Essayez d’ajouter des constructeurs et des méthodes publiques qui peuvent être remplacés.

Votre objet aura besoin de données exta (cachées) pour gérer la répartition virtuelle. Par conséquent, il est plus gros que ce qu'il semble être et par conséquent, offset a des problèmes,

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X