2017-01-25 5 views
3

Je suis préoccupé par la sécurité des conversions de types Je suis en train de concevoir une interface abstraite, qui sera supportée par les plugins exportant un objet orienté C ABI , c.-à-d. des pointeurs vers des objets et des fonctions de style C de la forme func(void *this, ...) plutôt que des fonctions membres de style C++, ils seront ensuite regroupés dans une structure représentant l'implémentation des objets. Cependant certains de mes cadres sous-jacents, utilisent l'héritage virtuel multiple.Transmettre un objet C++ (avec un héritage virtuel multiple possible) via un C ABI via le pointeur

Exemple simplifié

class A 
{ 
    public: 
     virtual void doA() 
} 

class B 
{ 
    public: 
     virtual void doB() 
} 

class C : public A, public B 
{ 
    public: 
     virtual void doA() 
     virtual void doB() 
} 

struct impA 
{ 
    (*doA)(void *self); 
} 

struct impB 
{ 
    (*doB)(void *self); 
} 

struct impC 
{ 
    (*doA)(void *self); 
    (*doB)(void *self); 
} 

void * AfromC(void *v) { 
    C*c = reinterpret_cast<C*>(v); // Known to be C* type 
    return static_cast<void*>(static_cast<A*>(c)); // method 1 
    return reinterpret_cast<void*>(static_cast<A*>(c)); // method 2 

    //method 3 & 4 
    C** c = static_cast<C**>(v); // Known to be C* type 
    return static_cast<void*>(&static_cast<A*>(*c)); // method 3 
    return static_cast<void*>(static_cast<A**>(c)); // method 4 
} 

/////////// main code 

class A 
{ 
    public: 
     void doA() { imp.doA(self); } 
    private: 
     impA imp; 
     void *self; 
} 

class B 
{ 
    public: 
     void doB() { imp.doB(self); } 
    private: 
     impB imp; 
     void *self; 
} 

Tenir compte AfromC, j'ai 4 méthodes possibles d'obtenir un pointeur que je peux en toute sécurité passer par un C ABI, je veux connaître la contrepartie de ces différentes méthodes, ma préférence serait méthode 1.

Je ne suis pas sûr si toutes ces méthodes sont légales ou sûres.

Note: objet sera toujours l'accès par des fonctions dans le binaire dont ils sont créés/détruits leur retour/accepter d'autres objets manipulés par les types de données auto ou de style C (jusqu'à struct de POD)

Bien que j'ai trouvé mention de telles choses sur le net, elles concernent toutes des personnes ayant des problèmes à cause de la conversion en A* a= static_cast<A*>(static_cast<void*>(c)) // c -> C*, ce qui est prévisible car cela ne corrige pas le vtable et les solutions sont d'utiliser le résumé. type de base (cela ne fonctionne pas pour moi car j'ai besoin de passer par le C ABI), mais j'ai aussi entendu parler de pointeurs virtuels étant plus grands que les pointeurs normaux d'où ma raison de considérer les méthodes 3 et 4, comme cela serait un pointeur normal vers le plus grand pointeur et donc sûr même pour les types avec des pointeurs plus grands.

Donc, ma question principale est la méthode 1 fonctionnera sans problème? Aussi, pourrais-je définir en toute sécurité une fonction de gabarit sur le modèle de template <typename T, typename U> void * void_cast(U v) { static_cast<void *>(static_cast<T>(v)); } pour simplifier le code du plugin. Enfin si la méthode 1 est correcte pourquoi? et l'une des méthodes peut-elle être utilisée?

Répondre

1

La règle est que vous pouvez convertir avant et en arrière d'un pointeur vers un objet à pointeur vers sa classe de base, et d'un pointeur vers un objet à void *. Mais il n'y a aucune garantie que tous ces pointeurs gardent la même valeur (ni même la même représentation)!

Dit autrement avec des exemples où C est dérivé de A:

C* c = new C; 
A* a = static_cast<A*>(c); // legal 
C* c1 = static_cast<C*>(a); // ok c1 == c guaranteed 

void *vc = static_cast<void *>(c); // legal 
C* c2 = static_cast<C*>(vc); // ok, c2 == c guaranteed 

void *va = static_cast<void *>(a); // legal, but va == vc is not guaranteed 

a2 = static_cast<A*>(vc); // legal, but a2 == a not guaranteed 
          // and dereferencing a2 is Undefined Behaviour 

Cela signifie que si v est construit comme void *v = static_cast<void *>(c); puis passé à votre AfromC la méthode static_cast<A*>(v) peut ne pas pointer vers un objet valide. Et les deux méthodes (1) et (2) sont no-op parce que vous avez lancé de void* à un pointeur vers obj et back qui est nécessaire pour obtenir la valeur d'origine.

Pour la méthode (4), mais vous lancez un pointeur vers void vers un pointeur vers un pointeur, depuis un pointeur vers un pointeur vers un pointeur vers un pointeur, puis de nouveau vers void. En tant que 3.9.2 types de composés [basic.compound] déclare:

3 ... Pointeurs aux schémas compatibles types ont la même représentation de la valeur et exigences d'alignement ...

Comme tous pointeurs sont disposition types compatibles, la deuxième opération ne devrait pas changer la valeur et nous sommes de retour dans la non-op de la méthode (1) et (2)

méthode (3) ne devrait même pas compiler, parce que vous prenez l'adresse d'un static_cast et ce n'est pas une lvalue. TL/DR: les méthodes (1), (2) et (4) sont no-op, ce qui signifie que vous retournez la valeur d'entrée inchangée et la méthode (3) est illégale car l'opérateur & requiert une lvalue.

La seule façon realiable de convertir un vide * pointant vers un objet C à quelque chose qui pourrait être en toute sécurité converti en A * est:

void * AfromC(void *v) { 
    C* c = static_cast<C*>(v); // v shall be static_cast<void>(ptr_to_C) 
    A* a = static_cast<A*>(c); 
    return static_cast<void *>(a); // or return a; with the implicit void * convertion 
} 

ou comme une seule expression de ligne

void * AfromC(void *v) { 
    return static_cast<A*>(static_cast<C*>(v)); 
} 
+0

Pouvez-vous clarifier "mais va == vc n'est pas garanti" étant donné que je rends va à A * = static_cast (va), puis-je ensuite convertir ceci en un objet de type C soit static_cast (a) ou reinterpret_cast (a), étant donné que je sais que va avait été converti en y notre réponse d'un objet de type C, ou ce ne serait plus sûr après avoir coulé le type de vide? – glenflet

+0

"dereferencing a2 is Undefined Behavior" vous explique pourquoi vous pensez cela? D'après ce que je peux dire, dans une vue générique, 'a2' et' c2' dérivent identiquement de 'a' et' c', donc ils devraient être également UB - soit oui ou non, pas à mi-chemin. – dascandy

+0

@glenflet: Aucune garantie pour cela ne peut être trouvée dans la norme. Et dans les implémentations utilisant vtables pour les fonctions virtuelles, il est courant que l'adresse de l'objet soit l'adresse de la vtable. Dans ce cas, static_cast effectue la correction de décalage, mais la valeur réelle de 'va' et' vc' est différente dans ce cas. Essayez simplement avec un débogueur et des sous-classes et des sous-classes ayant des fonctions virtuelles –

0
return static_cast<void*>(static_cast<A*>(v)); // method 1 
return reinterpret_cast<void*>(static_cast<A*>(v)); // method 2 

Si void* v des points à une instance de type C, alors static_cast<A*>(v) est faux.

//method 3 & 4 
C** c = static_cast<C**>(v); // Known to be C* type 
return static_cast<void*>(&static_cast<A*>(*c)); // method 3 
return static_cast<void*>(static_cast<A**>(c)); // method 4 

Si void* v des points à une instance de type C, alors static_cast<C**>(v) est faux.


Soyez très prudent sur la coulée void* dans le bon type de l'objet pointu. Je préférerais utiliser static_cast quand je peux, au lieu de reinterpret_cast. Je préfère également les conversions implicites pour l'accès de sous-objet de base et pour la conversion en void*. La plaque chauffante réduite est moins fatigante pour les yeux.

void* AfromC(void* v) { 
    C* c = static_cast<C*>(v); // Known to be C* type 
    A* a = c;     // point to base sub object 
    return a;     // implicit conversion to void* 
}