Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the call of virtual functions and the internal layout of objects in C++?

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

Many novices are not very clear about the call of virtual functions and the internal layout of objects in C++. In order to help you solve this problem, the following editor will explain it in detail. People with this need can come and learn. I hope you can get something.

This time I still use the assembly code generated by analyzing the compiled C++ code to explain the implementation of virtual function calls in C++, and also explain the internal layout of objects in C++ by the way. All of the assembly code below is compiled in VC2005. Although different compilers may produce different results and the internal layout of objects may be different, as long as the compilers meet the C++ standards, the compilation results and the internal layout of objects should be more or less the same.

First, there are two classes with a simple inheritance relationship: class CBase

{

Public:

Virtual void VFun1 () = 0

Virtual void VFun2 () = 0

Void Fun1 ()

}

/ / this is only to generate the assembly code for the function, so the function body is empty.

Void CBase::Fun1 ()

{

}

Class CDerived: public CBase

{

Public:

Virtual void VFun1 ()

Virtual void VFun2 ()

Void Fun2 ()

Private:

Int m_iValue1

Int m_iValue2

}

/ / this is only to generate the assembly code for the function, so the function body is empty.

Void CDerived::VFun1 ()

{

}

/ / this is only to generate the assembly code for the function, so the function body is empty.

Void CDerived::VFun2 ()

{

}

/ / this is to analyze the internal layout of the object, so just assign values to the member variables

Void CDerived::Fun2 ()

{

M_iValue1 = 13

M_iValue2 = 13

}

Now call the member function with the following code:

CDerived derived

/ / call virtual functions with objects

Derived.VFun1 ()

Derived.VFun2 ()

/ / call non-virtual functions with objects

Derived.Fun1 ()

Derived.Fun2 ()

/ / implement polymorphism by calling virtual functions with pointers to the base class of the derived class

CBase * pTest = & derived

PTest- > VFun1 ()

PTest- > VFun2 ()

Here is the assembly code generated after compiling the above code with VC2005:

CDerived derived

0041195E lea ecx, [derived]

00411961 call CDerived::CDerived (411177h)

/ / Code snippet 1

Derived.VFun1 ()

00411966 lea ecx, [derived]

00411969 call CDerived::VFun1 (411078h)

Derived.VFun2 ()

0041196E lea ecx, [derived]

00411971 call CDerived::VFun2 (4111B8h)

Derived.Fun1 ()

00411976 lea ecx, [derived]

00411979 call CBase::Fun1 (411249h)

Derived.Fun2 ()

0041197E lea ecx, [derived]

00411981 call CDerived::Fun2 (4111BDh)

/ / Code snippet 2

CBase * pTest = & derived

00411986 lea eax, [derived]

00411989 mov dword ptr [pTest], eax

PTest- > VFun1 ()

0041198C mov eax,dword ptr [pTest] / / Line 1

0041198F mov edx,dword ptr [eax] / / Line 2

00411991 mov esi,esp

00411993 mov ecx,dword ptr [pTest]

00411996 mov eax,dword ptr [edx] / / Line 3

00411998 call eax / / Line 4

0041199A cmp esi,esp

0041199C call @ ILT+495 (_ _ RTC_CheckEsp) (4111F4h)

PTest- > VFun2 ()

004119A1 mov eax,dword ptr [pTest]

004119A4 mov edx,dword ptr [eax]

004119A6 mov esi,esp

004119A8 mov ecx,dword ptr [pTest]

004119AB mov eax,dword ptr [edx+4] / / Line 5

004119AE call eax

004119B0 cmp esi,esp

004119B2 call @ ILT+495 (_ _ RTC_CheckEsp) (4111F4h)

Through the observation of code snippet 1, we can find that calling the virtual member function of the class through the object is the same as calling the non-virtual member function (the analysis of the assembly code that calls the member function can see my article "Analysis of the this pointer in C++"). In other words, it is impossible to achieve polymorphism with objects.

The following is mainly to analyze the code snippet 2 that implements polymorphism.

Line 1. Put the contents of the first two words of the address pointed to by the pTest pointer (4 bytes, that is, the size of a pointer in a 32-bit system) into the eax register as a pointer

Line 2. Put the value of the pointer in the eax register into the edx register

Line 3. Put the value of the pointer in the dex register into the eax register

Line 4. Call the function pointed to by the eax register

This analysis doesn't seem very clear about how to call the virtual function VFun1 () of the object derived. So let's take a look at the following picture:

This diagram is a hypothetical internal layout of the object derived in memory. The pointer pTest points to the object derived, while the first four bytes of the object derived are a virtual table pointer to the virtual function table.

Looking at this picture and analyzing the assembly code above, it will be much clearer:

Line 1. Get the virtual table pointer value and put it in the eax register

Line 2. Get the value of the virtual table pointer and put it in the edx register

Line 3. Get the value of the address pointed to by the virtual table pointer (that is, VFun1) and put it in the eax register

Line 4. Call the function pointed to by the eax register

Line 5 proves the assumption of the virtual function table in the figure above. The address of the second virtual function VFun2 () is obtained by adding 4 (the size of a pointer in a 32-bit system) to the address of the first virtual function VFun1 ().

Through the above analysis, we can get the calling method of the virtual function in C++: first, get the virtual table pointer in the object; then, find the corresponding virtual table through the virtual table pointer; finally, find the corresponding function to call through the offset in the virtual table.

The following is to prove the existence of the pointer to the virtual function table in the above figure by analyzing the non-virtual member function Fun2 () of class CDerived.

Void CDerived::Fun2 ()

{

004118F0 push ebp

004118F1 mov ebp,esp

004118F3 sub esp,0CCh

004118F9 push ebx

004118FA push esi

004118FB push edi

004118FC push ecx

004118FD lea edi, [ebp-0CCh]

00411903 mov ecx,33h

00411908 mov eax,0CCCCCCCCh

0041190D rep stos dword ptr es: [edi]

0041190F pop ecx

00411910 mov dword ptr [ebp-8], ecx

M_iValue1 = 13

00411913 mov eax,dword ptr [this] / / Line 6

00411916 mov dword ptr [eax+4], 0Dh / / Line 7

M_iValue2 = 13

0041191D mov eax,dword ptr [this]

00411920 mov dword ptr [eax+8], 0Dh

}

00411927 pop edi

00411928 pop esi

00411929 pop ebx

0041192A mov esp,ebp

0041192C pop ebp

0041192D ret

The above is the assembly code for the non-virtual member function Fun2 () of the class CDerived. As you can see, line 6 puts the address pointed to by this in the eax register, while line 7 assigns a value to the address pointed to by the this pointer plus 4 (for a specific analysis, see "the this pointer in C++"), and this address stores the first member variable of class CDerived. We know that the this pointer points to the first address of the object, so why move back 4 bytes when assigning a value to the first member variable? The answer is because the first four bytes of the object are used to store virtual table pointers.

The following code is the C++ code and compiled assembly code for the class without virtual functions in the article "analyzing the this pointer in C++":

Class CTest

{

Public:

Void SetValue ()

Private:

Int m_iValue1

Int m_iValue2

}

Void CTest::SetValue ()

{

M_iValue1 = 13

M_iValue2 = 13

}

Void CTest::SetValue ()

{

004117E0 push ebp

004117E1 mov ebp,esp

004117E3 sub esp,0CCh

004117E9 push ebx

004117EA push esi

004117EB push edi

004117EC push ecx

004117ED lea edi, [ebp-0CCh]

004117F3 mov ecx,33h

004117F8 mov eax,0CCCCCCCCh

004117FD rep stos dword ptr es: [edi]

004117FF pop ecx

00411800 mov dword ptr [ebp-8], ecx

M_iValue1 = 13

00411803 mov eax,dword ptr [this] / / Line 8

00411806 mov dword ptr [eax], 0Dh / / Line 9

M_iValue2 = 13

0041180C mov eax,dword ptr [this]

0041180F mov dword ptr [eax+4], 0Dh

}

00411816 pop edi

00411817 pop esi

00411818 pop ebx

00411819 mov esp,ebp

0041181B pop ebp

0041181C ret

Through the comparison of line 8, line 9 and line 6, line 7, we can see that the first 4 bytes of the object of the class CTest store its first member variable; while the object of the class CDerived stores its first member variable from the fifth byte, and its first 4 bytes are used to store the virtual table pointer. This once again proves the correctness of the internal layout of the objects in the above figure.

Is it helpful for you to read the above content? If you want to know more about the relevant knowledge or read more related articles, please follow the industry information channel, thank you for your support.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report