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 pointer to the C++ member function?

2025-03-04 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

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

What this article shares with you is about what the pointer of C++ member function is. The editor thinks it is very practical, so I share it with you to learn. I hope you can get something after reading this article. Let's take a look at it with the editor.

The C++ language supports the language mechanism of pointers to member functions. Like many other C++ language mechanisms, it is also a double-edged sword, which can be used well and can improve the flexibility and expansibility of the program, etc., but there are also some pitfalls that are not easy to find, and we need to pay more attention when using it, especially when we use it in combination with other language mechanisms of C++.

Keywords: member function pointer, inheritance, virtual function, this pointer adjustment, static_cast

2 Syntax C++ member function pointer (pointer

The usage of to member function) is similar to that of function pointers in C language.

The following code illustrates the general use of member function pointers:

Class ClassName {public: int foo (int);}

Int (ClassName::*pmf) (int)

= & ClassNmae::foo

The use of ClassName c; / /. *, which is called through the object

(c.*pmf) (5) / / A

The use of ClassName * pc = & c; / /-> *, called through an object pointer

(Pc- > * pmf) (6); / / B

Using typedef can make your code look a little bit better:

Typedef int (ClassName::*PMF) (int)

PMF pmf = & ClassName::foo

Note that the syntax for getting a member function pointer is very strict:

1)

You cannot use parentheses: for example, & (ClassName::foo) is not correct.

2)

Must be limited: for example, & foo is not correct. Not even within the scope of the ClassName class.

3)

Address symbols must be used: for example, it is not possible to write ClassName::foo directly. (although ordinary function pointers can do this)

So, you have to write: & ClassName::foo.

The call to a C++ member function requires at least three elements: this pointer, function argument (perhaps null), and function address. In the above call, the object / pointer before the-> * and. * operators provides this (which is not exactly the same as actually using this, as discussed later), parameters are provided in parentheses, and pmf provides the address of the function.

Notice here that the member function pointer has begun to show its "alien" nature. The two expressions at comments An and B in the above code produce a "thing" with no type in C++ (this is the only exception in the C++ language, everything else is typed). These are the. * and-> * operators:

(c.*pmf)

(Pc- > * pmf)

The "thing" generated by the evaluation of these two operators, we only know that we can use it as a function call, and we can't do anything else or even store it somewhere. For this reason, Andrei Alexandrescu said in his famous "Modern C++ design" that the member function pointer and these two operation symbols are "curiously half-baked concept in C++". (section 5.9)

The concept of "reference" is introduced into C++, but there is no "reference to member function", which is also a special place. (of course, we can use the reference of "member function pointer", hehe)

(3) the mixed use of C++ with other language mechanisms is a language of Multi-Paradigm, and it is common for various language mechanisms to be mixed. Here we only mention a few language mechanisms that affect the implementation and operation of member function pointers.

According to the C++ language, the member function pointer has the contravariance feature, that is, the member function pointer of the base class can be assigned to the member function pointer of the inherited class. The C++ language provides the default conversion method, but not the other way around.

3.2 the virtual function should be explained first, pointing to the virtual member function (virtual function)

The pointer of member) can also correctly show the characteristics of virtual functions. Examples are as follows:

Class B {public:

Virtual int

Foo (int) {/ * bones

Implementation * / return 0;}}

Class D: public

B {public: virtual

Int foo (int) {/ * dudes implementation * / return

0;}}

Int

(B::*pmf) (int) = & B::foo

D d

B* pb = & d

(d.*pmf) (0); / / execute D::foo here

(pb- > * pmf) (0); / / execute D::foo here, polymorphisms

C++ provides run-time polymorphism through virtual functions, and the implementation of virtual functions is very different from ordinary functions. General compilers use the familiar v-table (virtual function table) approach. All virtual function addresses are stored in a function table, and the class object stores the first address of the function table (vptr_point). The runtime finds the function call address based on the this pointer, the virtual function index, and the virtual function table pointer.

3.2 Multi-inheritance because of these differences, when the member function pointer encounters a virtual function, it also needs special treatment in order to correctly show the desired virtual properties.

Multi-inheritance is involved here because the existence of multi-inheritance leads to the complexity of the implementation of member function pointers. This is because the compiler sometimes needs to adjust the "this" pointer.

Examples are as follows:

Class B1 {}

Class B2 {}

Class D: public

B1, public B2 {}

Assuming that none of the above three objects involve virtual functions, the typical layout of D in memory is shown in the following figure (if there is a virtual function, there is an extra vptr pointer, which is not different).

Now suppose we call the function of B2 through the D object.

D d

D.fun_of_b2 ()

The this pointer passed to fun_of_b2 cannot be & d, but an offset should be added to & d to get the first address of the B2 sub-object contained in D.

The implementation of member function pointers must take this situation into account.

Multi-inheritance is always unpopular. However, even if it is a single inheritance, the above situation will happen. Consider the following example:

Class B {}; / / non-virtual

Class

Class D: public B {}; / / virtual class

Suppose B is a normal class with no virtual member functions. And D adds a virtual member function.

Because D introduces the vptr pointer, and the general implementation puts the vptr at the beginning of the object, it is still necessary to adjust the this pointer when accessing the member function of B through the D object.

D d

The d.fun_of_b (); / / this pointer also needs to be adjusted, otherwise the behavior of fun_of_b will be abnormal

4 implementation as we can see from the previous section, there are several problems that cannot be bypassed by the compiler to implement member function pointers:

1) whether the function is a virtual function, this involves the access to the virtual function table (_ _ vtbl).

2) whether and how to adjust the this pointer when the function is running. This involves the memory layout of C++ objects.

In fact, member function pointers must remember these two pieces of information. There is no need to explain why you should remember whether it is a virtual function or not. But why remember the this pointer adjustment? Because it must be used when evaluating the. * and-> * operators. Consider the above example of multiple inheritance:

Int (D::*pmf) (int) = & B2VANG foodies of B2; / / A

D d

(d.*pmf) (0)

/ / B

Looking at the code above, we actually know at A that the this pointer needs to be adjusted, and we also know how to adjust it. But at this time, this has not yet been born, and it is not time to adjust. Finally there is a This pointer at B, but I don't know how to adjust it. So pmf must remember the adjustment mode, and then adjust it when it is called at B.

4.1 Microsoft implementation 4.1.1 internally indicates that the implementation of Microsoft VC uses the Thunk technology that Microsoft has always used (I don't know how the name came from, but it's interesting to spell it the other way around to become Daniel Knuth's name, hehe).

For Mircosoft, there are actually two kinds of member function pointers, one needs to adjust the this pointer, and the other does not need to adjust the this pointer.

First identify the situations in which the member function pointer needs to be adjusted to the this pointer, and those cases are not needed. Recalling the description of the memory layout of C++ objects discussed in the previous section, we can draw the following conclusions:

If a class object obj contains some sub-objects subobj, the first address of these sub-objects & subobj and the object's own first address & obj, you may need to adjust the this pointer. Because it is possible to use subobj's function as obj's own function.

According to this principle, you can know that the this pointer does not need to be adjusted in the following situations:

1)

Inherits the top-level class of the tree.

2)

Simple inheritance, if all classes do not contain virtual functions, then all classes on the inheritance tree do not need to adjust the this pointer.

3)

Single inheritance, if the topmost class contains virtual functions, then all classes on the inheritance tree do not need to adjust the this pointer.

This pointer adjustments may be made under the following circumstances:

1)

Multiple inheritance

2)

Single inheritance, the top base class does not contain virtual function, but the inheritance class contains virtual functions. Then these inherited classes may need to make this pointer adjustments.

Microsoft makes a clear distinction between the two situations. So the internal representation of member functions can be roughly divided into the following two types:

Struct

Pmf_type1 {

Void* vcall_addr

}

Struct

Pmf_type2 {

Void* vcall_addr

Int

Delta; / / adjust the this pointer for

}

These two representations may cause the size of the member function pointer to be different, with a pmf_type1 size of 4 and a size of 8. You can write a piece of code to test it if you are interested.

4.1.2 Vcall_addr implementation vcall_addr appears in the above two structures, which is the core of Microsoft's Thunk technology. To put it simply, vcall_addr is a pointer that hides the difference between whether the function it refers to is a virtual function or an ordinary function. In fact, if the member function it refers to is an ordinary member function, then this address is the function address of the member function. In the case of a virtual member function, the pointer points to a small piece of code that finds the real function address based on the this pointer and the virtual function index value, and then jumps (note that it jumps to jmp, not the function call call) to execute at the real function address.

Look at an example.

/ / Source code

Class C

{

Public:

Int

Nv_fun1 (int) {return

0;}

Virtual

Int v_fun (int)

{return 0;}

Virtual

Int v_fun_2 (int)

{return 0;}

}

Void foo (C * c)

{

Int

(C::*pmf) (int)

Pmf = & C::nv_fun1

(C-> * pmf) (0x12345678)

Pmf = & C::v_fun

(C-> * pmf) (0x87654321)

Pmf = & C::v_fun_2

(C-> * pmf) (0x87654321)

}

Foo assembly code, release version, optimized in some places

: 00401000 56 push esi

: 00401001 8B742408 mov esi, dword ptr [esp+08]

; pmf = & C::nv_fun1

; (c-> * pmf) (0x12345678)

: 00401005 6878563412 push 12345678

: 0040100A

8BCE mov ecx, esi; this

: 0040100CE81F000000 call 00401030

; pmf = & C::v_fun

; (c-> * pmf) (0x87654321)

: 00401011 6821436587 push 87654321

: 00401016 8BCE mov ecx, esi; this

: 00401018 E803070000 call 00401720

; pmf = & C::v_fun_2

; (c-> * pmf) (0x87654321)

: 0040101D 6821436587 push 87654321

: 00401022 8BCE mov ecx, esi; this

: 00401024 E807070000 call 00401730

: 00401029 5E pop esi

: 0040102A

C3 ret

: 00401030

33C0; function implementation xor eax, eax

: 00401032

C20400

Ret 0004

: 00401720

8B01

Vcall mov eax, dword

Ptr [ecx]

: 00401722 FF20 jmp dword ptr [eax]

: 00401730

8B01

Vcall mov eax, dword

Ptr [ecx]

: 00401732 FF6004 jmp [eax+04]

The use of vcall_addr can be seen from the assembly code above. 00401030, 00401720, and 00401730 are all values of vcall_addr, which is actually the value of pmf. At the place of the call, we can't tell whether it is a virtual function or not, all we see is a function address. But after vcall_addr is called as a function address, enter vcall_addr, and there is a difference. 00401720

00401730 is the vcall of two virtual functions, both of which calculate the function address based on the this pointer, and then jmp to the real function address. 00401030 is the real address of C::nv_fun1.

This implementation of Microsoft needs to generate such a piece of code for each virtual function used in a class. This is like a template function:

Template

Void

Vcall (void* this)

{

Jmp this- > vptr [index]

/ / pseudo asm code

}

Each different index produces an instance.

Microsoft implements the call of virtual member function pointer in this way.

4.1.3 This pointer adjustment, but there is still a problem with this adjustment, which we haven't solved. In order to simplify the above example, we deliberately avoid this pointer adjustment. However, with the above foundation, it will be easy for us to discuss this pointer adjustment.

First we need to construct a situation that requires this pointer adjustment. Recall that at the beginning of this section, we discussed the situations where this pointer adjustment is required. We use an example of single inheritance to illustrate. Let's not consider the problem of avoiding virtual/non-virtual function this time.

Class B {

Public:

B (): MSBB (0x13572468) {}

Int

B_fun (int) {

Std::cout

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