Exploration of C ++ object layout and multi-State implementation (2)

Source: Internet
Author: User
Object layout of classes with virtual functions (2)

Next, let's look at multi-inheritance. Define two classes, each containing a virtual function and a data member. Then, an empty subclass is derived from these two classes.
Struct c041
{
C041 (): C _ (0x01 ){}
Virtual void Foo () {C _ = 0x02 ;}
Char C _;
};
Struct c042
{
C042 (): C _ (0x02 ){}
Virtual void foo2 (){}
Char C _;
};
Struct c051: Public c041, public c042
{
};
Run the following code:
Print_size_detail (c041)
Print_size_detail (c042)
Print_size_detail (c051)
Result:
The size of c041 is 5
The detail of c041 is 64 B3 45 00 01
The size of c042 is 5
The detail of c042 is 68 B3 45 00 02
The size of c051 is 10
The detail of c051 is 6C B4 45 00 01 68 B4 45 00 02
Note: First, we observe the object output of c051 and find that it is 10 bytes in size. This indicates that it has two virtual table pointers. we can infer from the exported memory data, the first is a virtual table pointer, followed by a member variable inherited from c041. The value is also the value 0x01 that we assigned in the c041 constructor, and then a virtual table pointer, A member variable inherited from c042 with a value of 0x02.
For verification, run the following code:
C041 c041;
C042 c042;
C051 c051;
Print_vtable_item (c041, 0, 0)
Print_vtable_item (c042, 0, 0)
Print_vtable_item (c051, 0, 0)
Print_vtable_item (c051, 5, 0)
Note the second parameter of the last line, 5. It is the offset value from the starting address of the object to the virtual table pointer (calculated in bytes). From the output of the object memory, we can see that the size of c041 is 5 bytes, therefore, the starting position of the second virtual table pointer in c051 is 5 bytes away from the object address. The output result is:
(Note: This offset value is determined by observation and is not universal. It depends on the structure member alignment method used by the compiler to generate code as we mentioned earlier, we set this value to 1. If it is set to another value, the object size and the offset value will be affected. See the description at the beginning of article 1. The same below .)
C041: objadr: 0012fb88 vpadr: 0012fb88 vtadr: 0045b364 vtival (0): 0041df1e
C042: objadr: 0012fb78 vpadr: 0012fb78 vtadr: 0045b368 vtival (0): 0041d43d
C051: objadr: 0012fb64 vpadr: 0012fb64 vtadr: 0045b46c vtival (0): 0041df1e
C051: objadr: 0012fb64 vpadr: 0012fb69 vtadr: 0045b468 vtival (0): 0041d43d
Now we can see that the two virtual table pointers of c051 point to two non-existing virtual tables (vtadr columns in rows 3rd and 4 ), the entries in the virtual table are equal to c041 and c042 (that is, their two parent classes) the value of the virtual table entry (the value of the vtival column is the same for 1st, 3, and 2 and 4 rows ).
Why do subclass have two virtual tables instead of combining them into one. This object layout makes it easier to adjust the pointer when processing type Dynamic conversions. We will see this example later.

What if the subclass overrides the virtual function of the parent class? We have seen the previous class c071 once. Define another class c082 derived from c041 and c042, rewrite the virtual functions in the two parent classes, and add a virtual function.
Struct c041
{
C041 (): C _ (0x01 ){}
Virtual void Foo () {C _ = 0x02 ;}
Char C _;
};
Struct c042
{
C042 (): C _ (0x02 ){}
Virtual void foo2 (){}
Char C _;
};
Struct c082: Public c041, public c042
{
C082 (): C _ (0x03 ){}
Virtual void Foo (){}
Virtual void foo2 (){}
Virtual void foo3 (){}
Char C _;
};
Run the code similar to the above:
Print_size_detail (c082)
C041 c041;
C042 c042;
C082 c082;
Print_vtable_item (c041, 0, 0)
Print_vtable_item (c042, 0, 0)
Print_vtable_item (c082, 0, 0)
Print_vtable_item (c082, 5, 0)
Result:
The size of c082 is 11
The detail of c082 is 70 B3 45 00 01 6C B3 45 00 02 03
C041: objadr: 0012fa74 vpadr: 0012fa74 vtadr: 0045b364 vtival (0): 0041df1e
C042: objadr: 0012fa64 vpadr: 0012fa64 vtadr: 0045b368 vtival (0): 0041d43d
C082: objadr: 0012fa50 vpadr: 0012fa50 vtadr: 0045b370 vtival (0): 0041d87a
C082: objadr: 0012fa50 vpadr: 0012fa55 vtadr: 0045b36c vtival (0): 0041d483
Sure enough, the entry values in the two virtual tables of c082 are different from those in the parent class (vtival column) and point to the new function address after rewriting. Observe the size of c082 and the object memory. We can know that it does not generate a new virtual table for the newly defined virtual function foo3. So is the function address of foo3 added to the first virtual table of the class or the second virtual table? In the debugging status, expand the c082 object in the "local variables" window. We can see two virtual tables and their entries, but both virtual tables can only see the first entry. This should be a small bug in vc7.1ide. It seems that we only have to find another way to verify. We first print out the value at the second entry position in the two virtual tables. Run the following code.
Print_vtable_item (c082, 0, 1)
Print_vtable_item (c082, 5, 1)
The result is as follows:
C082: objadr: 0012fa50 vpadr: 0012fa50 vtadr: 0045b370 vtival (1): 0041d32f
C082: objadr: 0012fa50 vpadr: 0012fa55 vtadr: 0045b36c vtival (1): 0041d87a
Then we call the foo3 function:
C082.foo3 ();
View its assembly code:
004225f3 Lea ECx, [EBP + fffffb74h]
004225f9 call 0041d32f
The address after the 2nd call command is the address of the foo3 function (actually a jump command). We can know it against the previous output, the virtual table entry corresponding to the new virtual function defined by the subclass is added to the first virtual table of the subclass and is located after the virtual table entry inherited from the parent class.

Dynamic type conversion and forced type conversion

To verify the aforementioned dynamic type conversion (dynamic_cast conversion) and forced conversion of object types. We use the previously defined c041, c042, and c082 classes for verification.
Run the following code:
C082.c041: C _ = 0x05;
Print_vtable_item (c041, 0, 0)
Print_detail (c041, (c041) c082 ))
Print_vtable_item (c041) c082), 0, 0)
Print_vtable_item (c082, 5, 0)
C042 * PT = dynamic_cast <c042 *> (& c082 );
Print_vtable_item (* PT, 0, 0)
Rows 2nd and 5th print the information in the original object for convenience. Lines 3rd and 4 forcibly convert the c082 object type and print the converted object memory information and virtual table information respectively. In row 3, we used dynamic_cast to perform a dynamic type conversion from the pointer of the subclass to the pointer of the right parent class, and then printed the information of the object to which the Pointer Points.
Result:
C041: objadr: 0012fa74 vpadr: 0012fa74 vtadr: 0045b364 vtival (0): 0041df1e
The detail of c041 is 64 B3 45 00 05
(C041) c082): objadr: 0012f2a3 vpadr: 0012f2a3 vtadr: 0045b364 vtival (0): 0041df1e
C082: objadr: 0012fa50 vpadr: 0012fa55 vtadr: 0045b36c vtival (0): 0041d483
* PT: objadr: 0012fa55 vpadr: 0012fa55 vtadr: 0045b36c vtival (0): 0041d483
First, we will compare the last two rows. From the objadr column, we can know that PT is not the starting address of the c082 object, instead, it points to the address of the 2nd virtual table pointers of c082 (because the value of objadr in the last row is equal to the value of vpadr In the last 2nd rows ). The vpadr value in the penultimate row is the 2nd virtual table pointer of the c082 object (we specified the offset value 5 in the output ). This address is part of the c082 object inherited from the c042 class, that is, in addition to changing the type information during dynamic type conversion, the compiler also adjusts the pointer position, to ensure the correctness of the conversion semantics. Therefore, we can know that dynamic_cast must be used to convert the type of the pointer to a class object with a complex inheritance structure (generally in the inheritance tree for up or down conversion, it correctly handles pointer position adjustment. If the conversion is illegal, it returns a null pointer. Remember to perform this check when using dynamic_cast. In this article, we will save these checks for the sake of simplicity. This check can be defined by a macro to facilitate removal in the release version and improve efficiency.
Then compare the output of (c041) c082) and c082, we can find that the compiler generates a new temporary object to forcibly convert the object type, because their objadr columns are different, it indicates they are not the same object. Then observe the vtadr and vtival (0) of c041, (c041), c082, and c082. The first two rows are the same, and the last two rows are different. This also shows that when the compiler processes forced conversions, a new c041 object is actually generated. Because the forced type conversion of objects is not like the dynamic type conversion of pointers, the dynamic type conversion of pointers must also ensure the polymorphism semantics, so you only need to adjust the pointer position. The forced type conversion of objects also adjusts the entry values in virtual tables, because object type conversion does not require polymorphism. The first entry in the first virtual table of c082 class stores the address of the c082: Foo () function. After converting the object type, it should be adjusted to c041 :: foo () is correct. This adjustment is too complicated, so the compiler simply creates a new temporary c041 object. The last two columns of the three rows are known. I don't know if this is a behavior defined in the C ++ Standard Specification. I will update it the next day.
When the new object is added, the compiler also copies the values of the data members of the original object that belong to the parent class. Pay attention to the second line of the Code, c082.c041: C _ = 0x05;, we first rewrite the value of the data member inherited from the c041 class in the c082 object to 0x05, the original value is 0x01, which is initialized by the c041 constructor. We observe the output of the 2nd rows. As mentioned above, the printed object is not c082 but a temporary object generated by the compiler. we can note that the last field of the object is 0x05, that is, the value of the data member. So we know that in addition to the new temporary object, the compiler also copies the values of the corresponding data members in the original object.
This is a little different from my previous understanding. intuitively, I always thought that this type of conversion would not generate new objects, but it is also right to think about this method of the compiler. If no new objects are generated, it means that it dynamically changes the value of entries in the virtual table as described above. However, the new temporary object also means that calling with the following statements may produce unexpected results.
(C041) c082). somefun ();
If the somefun function changes the object state, the status of c082 will not be changed after the code above is executed. Because somefun actually changes the temporary object, the temporary object will be thrown away after execution. This is different from the intuitive understanding. It is generally considered that this call will act on the c082 object. To verify that we declare the following two classes.
Struct c010
{
C010 (): C _ (0x01 ){}
Void Foo () {C _ = 0x02 ;}
Char C _;
};
Struct c013: Public c010
{
C013 (): C1 _ (0x01 ){}
Void Foo () {C1 _ = 0x02 ;}
Char C1 _;
};
The two classes are inherited, each having a common member function with the same name. This function is used to rewrite the corresponding member variables of the class. We make the following calls:
C013 OBJ;
OBJ. Foo ();
(C010) OBJ). Foo ();
The first Foo call changes the C1 _ value, and the last call changes the C _ value. Intuitively, it is easy to think that after the above code is executed, the values of obj. C _ and obj. C1 _ are both 0x02. However, when we expand the OBJ object in the local Variable Window of the debugging environment, we can find that obj. C1 _ is 0x02, but obj. C _ is 0x01. The reason is that (c010) OBJ) actually produces a temporary object, so the call of the last row does not apply to the OBJ object.
Further, if we use the singleton mode in a class, and this class has an inheritance structure, when you want to call the parent class method by performing an upward transformation on the object in a program, a compilation error occurs because the temporary object of the parent class cannot be constructed. The premise is that the constructor of the parent class should be protected by protected, rather than private. Otherwise, the subclass cannot be constructed. I have not verified this because it is silly to call this method, but this possibility is not ruled out. The correct method for calling the last line in the above example should be:
OBJ. c010: Foo ();
In this way, you can call the override function in the parent class, and it also works on the correct object.

(To be continued)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.