Chaos In C ++ :: Pointers-to-Member Functions

zhaozj2021-02-16  94

Difficulty:

Preferring description: The following covers the implementation of the pointer to the member function in GCC 3.2 and MS Visual C 6 / .NET. If you read this article, don't forget the last point of the article.

Have you previously converted to a pointer to a member function into a long and the compiler refused? Here, the truth will be said. First, a quite "magic" code

Struct Base1

{

INT I;

Base1 (): i (1) {}

Void fun1 () {cout << i << endl;}

}

Struct Base2

{

INT I;

Base2 (): i (2) {}

Void fun2 () {cout << i << endl;}

}

Struct Derived: Public Base1, Public Base2, PUBLIC BASE2

{

INT I;

Derived (): i (3) {}

Void fun3 () {cout << i << endl;}

}

Typedef void (Derived :: * MEM_PTR) ();

Int main () {

MEM_PTR MEM_PTR = & Derived :: Fun2;

Derived D;

* (ReinterPret_cast (& MEM_PTR) 1) = 0;

(d. * MEM_PTR) ();

* (ReinterPret_cast (& MEM_PTR) 1) = 4;

(d. * MEM_PTR) ();

* (ReinterPret_cast (& MEM_PTR) 1) = 8;

(d. * MEM_PTR) ();

}

What is the program output?

Let's analyze this Derived

Derived THIS pointer, there are two situations

1, point to the Base1 section

When D.FUN1 () or D.FUN3 (), the THIS pointer obtained by these two member functions is part of the base1.

2, point to the part of Base2

The THIS pointer obtained by this member function is to the base2 part when D.FUN2 () occurs.

It can be seen from these two situations that the THIS pointer is adjusted when the member function is called for multiple inheritance objects. D.Fun1 () / d.fun2 () / d.fun3 () is compiled, there is enough type information for object D and these three member functions, and the compiler will automatically adjust the THIS pointer. Then, if the member function takes the address, when the (OBJ. * MEM_PTR) () or (PTR-> * MEM_PTR) () is called, the compiler cannot fully know which member function is pointing to the MEM_PTR, so the compiler cannot be The call of the class is directly adjusted, but it is placed in the running period and adjusts according to the environment. So what is the basis for this adjustment?

Cout << sizeof (MEM_PTR) << endl; // mem_ptr is TypeDef-name in the above code

Did you find it? MEM_PTR This pointer to the member function is 8-byte instead of what we often say is 4-Byte. The first 4-byte of MEM_PTR is the address of the function, and the post 4-byte is the amount that needs to be adjusted. We can see the model by pointing the pointer to the member function as the following

((Object Address Adjustment). * Function Address) (); or ((Object Address Adjustment) -> * Function Address) ();

In the above code * (ReinterPret_cast (& MEM_PTR) 1) actually represents the memory of the 4-byte, that is, the adjustment amount.

Mem_ptr = & derived :: fun2; MEM_PTR Pointing to Derived :: Fun2

* (ReinterPret_cast (& MEM_PTR) 1) = 0; set the adjustment amount to 0

(D. * MEM_PTR) (); pseudo code: (& D - 0). * MEM_PTR) (); The this pointer has not changed, so I accessed in FUN2 is actually Base1 :: i

* (ReinterPret_cast (& MEM_PTR) 1) = 4; set the adjustment amount to 4

(D. * MEM_PTR) (); pseudo code: ((& D - 4). * MEM_PTR) (); The this pointer change, so I accessed in FUN2 is actually Base2 :: i

* (ReinterPret_cast (& MEM_PTR) 1) = 8;

(d. * MEM_PTR) ();

Where the adjustment amount is 4 and 8, the nature is SizeOf (Base1) and Sizeof (Base1) Sizeof (Base2)

Now let's come back to a "magical" code.

Change the cout << I << endl in each member function of the above code; change to cout << i << '/ t' << this << Endl;, then in the last side of Main () pl to add D .fun1 (); d.fun2 (); d.fun3 ();, final compilation, you will get two groups of output, but in the group of 3, we will find that two output THIS pointers are different, why? Maybe you have already thought. The clue is in the above text.

What is the member function be defined as Virtual This world becomes?

The BASE1 :: FUN1 () and base2 :: fun2 () and base2 :: fun2 () and base2 :: fun2 () of "magic" code are defined as virtual functions. The above adjustments 4 and 8 are then set to SizeOf (Base1) and SizeOf (Base2), and finally compile. The program will be interrupted after outputting 1 and 2. Out of output 3, why?

Let's first understand the object model of Derived.

Among them, two VPTRs, VPTR1 is used by this part of the base1 and Derived, and VPTR2 is used by the base2 section.

MEM_PTR = & derived :: fun2; Take the address of Derived :: Fun2 (Note: Because Base2 :: Fun2 is a virtual function, its actual address can only be decided in the running period. So here is "intended" two words). One thing we can sure base2 :: fun2 is inserted in the first location in the base2 vTable.

* (ReinterPret_cast (& MEM_PTR) 1) = 0;

(D. * MEM_PTR) (); determined by the adjustment The THIS pointer points to the base1 section, and then try to get the first virtual function address in the first VTBL through the VPTR1, so in fact is BASE1 :: FUN1's address, Base1 pointer, call Base1 :: fun1, it will not be wrong, so output is 1

* (ReinterPret_cast (& MEM_PTR) 1) = sizeof (base1);

(D. * MEM_PTR) (); and the above configuration, the THIS is determined by the adjustment amount to point the base2 section, and the base2 :: fun2 address is obtained by VPTR2. So the output is 2

* (ReinterPret_cast (& MEM_PTR) 1) = sizeof (base1) sizeof (base2); (d. * MEM_PTR) ();

Why is the last one going wrong? Note that a feature that the THIS pointer is adjusted, and the VPTR points to which the adjusted THIS pointer is accessed. For Class Derived, we can learn that Derived derived part is not placed in the above object model, but a VPTR1 with Base1, so because the position referred to in the adjusted THIS is not Presence (correct) VPTR, so that the address of the error is found and mistakenly considers the VTBL of Base1, so an access error occurs. At this time, we can manually insert a data member to simulate a VPTR to achieve the original purpose.

Struct Derived: Public Base1, Public Base2, PUBLIC BASE2

{

INT VPTR; // Take a data member

INT I;

Derived (): i (3)

{

VPTR = * (ReinterPret_cast (this)); // Simulate a VPTR

}

Void fun3 () {cout << i << endl;}

}

The rest of the code is constant, then compiled. Now the program is correct, visit fun1 (), but the this pointer is not a part of Base1, in other words, although we succeed, but avoid this dangerous code.

So, if we try to convert the pointers that point to the member function through certain non-language, it is very dangerous.

Finally, we will make an experiment. Still use the first "magical" code. Add in the code

Struct Base3 {};

Struct Derived2: Public Base3, Public Derived {};

Then change the code in main to

int main ()

{

Void (Base3 :: * Pfunc1) ();

Void (derived2 :: * pfunc2) ();

PFUNC2 = PFUNC1;

PFUNC1 = PFUNC2;

}

Imagine why PFUNC2 = PFUNC1; can be compiled, and PFUNC1 = PFUNC2; but can't you?

The last point of explanation: GCC and MS Visual C are different from the pointer to the member function. In the MSVC, the size of the pointer to the member function is still 4-byte, that is, the size of the PFUNC1 is 4-Byte, and only the size of the pointer to the member function in multiple inherits is 8- Byte, that is, the size of PFUNC2 is 8-byte. In the GCC, it is 8-byte, which is in a single inheritance, its adjustment is 0.

About the acquisition of the virtual function address mentioned above, you can refer to

http://blog.9cbs.net/jinhao/archive/2004/01/17/4798.aspx

http://blog.9cbs.net/jinhao/archive/2004/01/17/4799.aspx

// the end

转载请注明原文地址:https://www.9cbs.com/read-13957.html

New Post(0)