Why is the member function pointer in C ++ 16 bytes?
When we talk about pointers, we usually assume that they are something that can be expressed using the void * pointer. In x86_64, they are 8 bytes in size. For example, the following is an excerpt from an article on x86_64 in Wikipedia: Pushes and pops on the stack are always in 8-byte strides, and pointers are 8 bytes wide. from the CPU point of view, the pointer is nothing more than the memory address. All memory addresses are represented by 64 bits on the x86_64 platform, so it is assumed that it is 8 bytes correct. By simply outputting the length of different types of pointers, it is not difficult to verify what we call. # Include <iostream> int main () {std: cout <"sizeof (int *) =" <sizeof (int *) <"\ n" "sizeof (double *) =" <sizeof (double *) <"\ n" "sizeof (void (*)()) = "<sizeof (void (*) () <std: endl;} compile and run the above program, from the result, we can see that the length of all pointers is 8 Bytes: $ uname-ix86_64 $ g ++-Wall. /example. cc $. /. outsizeof (int *) = 8 sizeof (double *) = 8 sizeof (void (*)()) = 8. However, in C ++, there is another special case -- the pointer of the member function. The member function pointer is twice the length of any other pointer. This can be verified by the following simple program. The output result is "16": # include <iostream> struct Foo {void bar () const {}}; int main () {std: cout <sizeof (& Foo: bar) <std: endl;} does this mean that Wikipedia is wrong? Apparently not! From the hardware point of view, all pointers are still 8 bytes. In this case, what is the pointer to a member function? This is a feature of the C ++ language. Here, the pointer of the member function is not directly mapped to the hardware. It is implemented by the runtime (compiler) and brings about some additional overhead, this usually results in performance loss. The implementation details are not mentioned in the C ++ language specification, and such type pointers are not explained. Fortunately, the Itanium C ++ ABI specification shares the implementation details of the C ++ runtime-for example, it explains how Virtual Table, RTTI, and exceptions are implemented, in section 2. 3 also explains the member pointer: A pointer to member function is a pair as follows: ptr: For a non-virtual function, this field is a simple function pointer. for a virtual function, it is 1 plus the virtual table offset (in bytes) of the function, represented as a ptrdiff_t. the value zero represents a NULL pointer, independent of th E adjustment field value below. adj: The required adjustment to this, represented as a ptrdiff_t. therefore, the member pointer is 16 bytes rather than 8 bytes, because after the simple function pointer, you also need to save how to adjust the "this" pointer (which is always implicitly passed to non-static member functions). The ABI specification does not explain why and when to adjust the this pointer. It may not be obvious at the beginning. Let's take A look at the following example of class inheritance: struct A {void foo () const {} char pad0 [32];}; struct B {void bar () const {} char pad2 [64] ;}; struct C: A, B {}; both A and B have A non-static member function and A data member. These two methods can be implicitly passed to their "this" pointer to access the data members in their classes. To access any data member, you need to add an offset to the "this" pointer. The offset is the offset from the data member to the base address of the class object, which can be expressed by ptrdiff_t. However, in multi-inheritance, things will become more complex. We have A class C that inherits A and B. What will happen? The compiler puts A and B in the memory at the same time, and B is under A. Therefore, the value of this pointer seen by methods of class A and B is different. This can be verified through practice, such as: # include <iostream> struct A {void foo () const {std: cout <"A's this: "<this <std: endl;} char pad0 [32] ;}; struct B {void bar () const {std :: cout <"B's this:" <this <std: endl;} char pad2 [64] ;}; struct C: A, B {}; int main () {C obj; obj. foo (); obj. bar ();} $ g ++-Wall-o test. /test. cc &&. /testA's this: 0x7fff57ddfb48B's this: 0x7fff57ddfb68 as you can see, the value of the "this" pointer is transmitted to B The method is 32 bytes larger than the method of A-the actual size of A Class A object. However, what happens when we use the following function to call the class C method through pointers? Void call_by_ptr (const C & obj, void (C: * mem_func) () const) {(obj. * mem_func) ();} is related to the called function. Different "this" pointer values are passed to these functions. However, the call_by_ptr function does not know whether its parameter is the foo () pointer or the bar () pointer. The only time to know this information is when these methods are used. This is why the pointer of a member function needs to know how to adjust this pointer before calling it. Now, we put all the data in A simple program and explained the internal working mechanism: # include <iostream> struct A {void foo () const {std :: cout <"A's this: \ t" <this <std: endl;} char pad0 [32] ;}; struct B {void bar () const {std: cout <"B's this: \ t" <this <std: endl;} char pad2 [64] ;}; struct C: a, B {}; void call_by_ptr (const C & obj, void (C: * mem_func) () const) {void * data [2]; std :: memcpy (data, & mem_func, sizeof (mem_func); std: cout <"------------------------------ \ N" "Object ptr: \ t" <& obj <"\ nFunction ptr: \ t "<data [0] <" \ nPointer adj: \ t "<data [1] <std: endl; (obj. * mem_func) ();} int main () {C obj; call_by_ptr (obj, & C: foo); call_by_ptr (obj, & C: bar );} the above program output is as follows: -------------------------- Object ptr: 0x7fff535dfb28Function ptr: 0x10c620cacPointer adj: 0A's this: 0x7fff535dfb28 then Obje Ct ptr: 0x7fff535dfb28Function ptr: 0x10c620cfePointer adj: 0x20B's this: 0x7fff535dfb48 I hope this article will make the problem clearer.