简体   繁体   中英

Same most-derived class in virtual inheritance = same offset between parent class?

For a certain class F , its pointer (created via new F() ) can be up-cast to a base's class pointer eg to B* , C* , D* and E* .

在此输入图像描述

Is it guaranteed that for a certain compiler (a certain configuration and a certain .exe program), difference of up-cast address (in bytes) of a pair of any mentioned classes (eg select 2 among B* , C* , D* and E* ) of every instance of new F() is a constant?

For example, this MCVE print 8 all 10 times for me :-

#include <iostream>
class B{ public: virtual ~B(){} };
class D: virtual public B{};
class C: virtual public B{};
class E: virtual public D{};
class F: virtual public E, virtual public C{};
int main(){
    for(int n=0;n<10;n++){
        F* f = new F();
        C* c=f;
        E* e=f;
        int offset=
            (reinterpret_cast<uintptr_t>(c))
            -
            (reinterpret_cast<uintptr_t>(e));
        std::cout<<offset<<std::endl;
    }
}

I believe the answer is yes, because I can static_cast it.
(and I shamefully rely on this assumption unconsciously for a long time.)

Different compiler may print different offset, but I care only a case of a certain (same) program and a certain (same) true underlying class ( new F() ).

I wish the value to always be constant in every situation. If so, I don't have to fix my program.

I would be glad if answer also quote some C++ specification.

Edit: Fix inconsistency in my code (Thank curiousguy 's comment)

[Foreword: Terminology:

For simplicity, we roughly follow the Itanium C++ ABI and relax/generalize terminology:

A proper base B of D is a real base class, distinct from D . An improper base is a proper base or D itself.

A proper subobject of a class D is a member or proper base; an improper subject is a proper subobject or D itself.

--end foreword]

[Foreword: Mistaken assumption:

You seem to be under the impression that expressing a type conversion as static_cast somehow guarantees that no complex code will be generated. This isn't the case, a static_cast can call anything a direct initialisation can, including calling a constructor: static_cast<String>("")

--end foreword]

There is no plausible reason for an implementation to not put every base class subobject at a fixed known offset in a complete (or most derived, which has the same layout in practice) object of type D ; so the question is: is there nothing preventing a perverse implementation from doing that?

What would it take for an implementation to have distinct layouts and would such implementation be conforming? We need to list the pointer movements (implicit conversions or casts) supported inside a class hierarchy.

[Note that access of an inherited (non static) data member is defined by an (implicit) conversion of this followed by access of a non inherited data member and so is a call to an inherited non static function.]

For:

  • X an (improper) base subobject of D
  • Y a (proper) base subobject of X (doesn't really matter that Y is proper actually)
  • Z is another (proper) base of D

ASCII-art summary (tree may be collapsed):

Y
|
X     Z
 \   /
   D

These bases must be unambiguous: the base subobject Y must be the one and only of that type in X . (But an indirect base Y of D doesn't have to unambiguous: D::Y may not designate a single base, only D::X::Y must be unambiguous.)

Three kinds of "simple" hierarchy movements must be supported:

  • (up) conversion (that can be done implicitly) of X* to Y*
  • (down NV) for a non virtual base Y of X: down cast of Y* to X* can be performed by static_cast
  • (down P) for (possibly virtual) polymorphic base Y of X: down cast of Y* to X* can be performed by dynamic_cast

Other more complex movement is a dynamic_cast for Y* to Z* ; it's two movements: down cast followed by up cast, going through the most derived object D , which doesn't have to be a statically known type. (Code performing these two steps explicitly would have to be able to name D .)

In general these operation are performed on at least partially constructed objects.

(The C++ standard has not made a clear decision on whether conversion to a pointer to a non virtual base is supported on a pointer to an un-constructed object. Anything involving a virtual base clearly cannot be done on an un-constructed object.)

So in practice the (improper) subobject X must carry enough information to locate its virtual bases, usually either by explicitly putting their addresses in hidden members, or by storing their offsets in the vtable.

[It implies that during construction of a proper base class with virtual bases, the vtable cannot be the same in general as the vtable for a complete object. This is unlike the construction of base subobjects (whether virtual or not) with only non virtual bases.]

If the virtual bases are located through the vtable, it means that there are no more possible class layouts then there are distinct vtables. The constructor of the most derived class would not be able to randomize the layout (unless vtables are emitted on the spot to the describe said layout).

If the virtual bases are located though hidden data members, it would seem that more flexibility for a perverse implementation exists. This is compounded by the fact that down casts from polymorphic virtual base must be supported: a polymorphic base only knows its dynamic type (through the vptr in all existing implementations). A derived class could store a array of addresses (or offset) (some, any) of its base classes, but a base cannot store information about each of its derived classes , by construction, as its layout must be defined before knowing which classes will be derived from it (it's easy to see that sizeof(T) cannot be, even in the most perverse implementation, a monotonic function of class definitions not used in T and that use T ).

But a perverse implementation could still support multiple layouts, by either of these approaches:

(multiple-vtables)

If vtables are generated on the spot, or if many vtables are created a priori to allow for different layouts of a class that has virtual bases, then a polymorphic base could have access to enough information to do any down cast.

[Note that there is already not a one to one mapping between uniques types and vtables in general, so the equality test of typeid , even of expressions of the same type, cannot be an address comparison between vptr in general. In general type equality is implemented by comparing typeinfo pointers.]

[Note that if you are using a "DLL" and the dynamic linker, various "equivalent" (identical in their symbolic definition before linking) type information tables (vtables and typeinfo structures) could exist at different addresses, but these non fusing linking break the ODR anyway as the static objects wouldn't be fused either.]

(BLIP)

Every polymorphic potential base class (that can possibly be used as a base class, so final or local classes could be exempt) would have at least one additional hidden member: a pointer (alternatively a relative offset) to a member in the most derived class: the (base-table) , an hidden table non static member.

The (base-table) would list (some, any) base class adresses (or offset), the base-locators (those bases the perverse implementations wants to reorder).

The BLIP (base-locators-info-ptr): a pointer to a typeinfo-like structure that contains the description of the base-locators layout, as the layout depends on the type of the most derived class which isn't known at compile time; note that the BLIP could be stored inside the vtable as its type dependent not instance dependent.

The down casts would locate the (base-table), which is opaque and impossible to interpret by code that only knows about the base class, and then use the BLIP to decode it, like the typeinfo data contains code to navigate in the base classes to implement down or up dynamic dynamic_cast .

This would seem exceptionally complicated and difficult to get right, and for what practical purpose?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM