上一篇文章我们讲解了成员指针的概念及用法,这里我们再深入来探讨一下。
我们知道一般的指针在32位平台上占4个字节,64位平台上占8个字节,但成员指针有点特殊,它不是一般的指针。对于指向数据的成员指针,其大小和一般指针一样,但其含义不太一样。对于指向成员函数的成员指针,在32位平台上它占8个字节,而在64位平台上占16个字节,这个我们可以通过 sizeof 很容易测得。
我这里的平台是64位,所以接下来的研究是基于64位平台的。那成员指针到底有何不同呢?这个在标准中没有多少说明,但在 Itanium C++ ABI里有这样一段解释:
A pointer to data member is an offset from the base address of the class object containing it,represented as a
ptrdiff_t
. It has the size and alignment attributes of aptrdiff_t
. A NULL pointer is represented as -1.A pointer to member function is a pair as follows:
ptr
:
- For a non-virtual function, this field is a simple function pointer. (Under current base Itanium psABI conventions, that is a pointer to a GP/function address pair.) 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 the adjustment field value below.adj
:- The required adjustment tothis, represented as a
ptrdiff_t
.
我给大家翻译一下:
一个指向数据成员的指针,它的类型是 ptrdiff_t,它里面存储的是该数据成员相对对象在内存中地址的偏移,其大小和对齐属性和 ptrdiff_t 一样(8字节)。-1 表示是空指针。
一个指向成员函数的指针拥有两个域(16字节),其含义如下:
ptr:
对于非虚函数,这个域是个简单的函数指针(在当前基本的 Itanium psABI 约定中,这个指针存储函数的地址)。对于虚函数, 其值是 1 加上该函数在虚函数表中的偏移(以字节为单位),其类型也是 ptrdiff_t。0 表示空指针,它和下面的 用于调整的域 adj 的值无关。
adj:
这个域是用来调整 this 指针的,其类型也是 ptrdiff_t。
ptrdiff_t 在内部的定义如下:
typedef long int ptrdiff_t;
看完这个解释后我们应该对成员指针的实现原理有所了解了,也清楚了指针大小为什么是这么多。我想各位对指向数据成员的指针可能问题不大,可能对指向成员函数的指针还有些疑问,下面我们通过代码来观察一下。
假设我们有这样一个类:
class Pairs {
public:
int a, b;
int max() const {
return a > b ? a : b;
}
virtual void show() const {
cout << "a = " << a << ", b = " << b << endl;
}
};
该类对象在内存中的布局应该是这样子的:
虚函数表地址(8字节) |
存储成员 a(4字节) |
存储成员b(4字节) |
我将根据这个类写一些测试代码,并附上输出结果。
示例代码一:
// 测试代码
int Pairs::*pa = &Pairs::a; // pa 存储 a 的 偏移
int Pairs::*pb = &Pairs::b; // pb 存储 b 的 偏移
cout << "sizeof(pa) = " << sizeof(pa) << endl;
cout << "pa's value = " << *(long *)&pa << endl;
cout << "pb's value = " << *(long *)&pb << endl;
// 输出结果
sizeof(pa) = 8
pa's value = 8
pb's value = 12
示例代码二:
// 测试代码
int (Pairs::*pfunc)() const = &Pairs::max;
long *temp = (long *)&pfunc;
cout << "sizeof(pfunc) = " << sizeof(pfunc) << endl;
printf("ptr of pfunc = 0x%08lx\n", temp[0]); // 输出的是 max 函数的地址
printf("adj of pfunc = 0x%08lx\n", temp[1]);
// 输出结果
sizeof(pfunc) = 16
ptr of pfunc = 0x0040ba8c
adj of pfunc = 0x00000000
示例代码三:
// 测试代码
void (Pairs::*pvirtual)() const = &Pairs::show;
long *temp = (long *)&pvirtual;
cout << "sizeof(pvirtual) = " << sizeof(pvirtual) << endl;
printf("ptr of pvirtual = 0x%08lx\n", temp[0]); // 输出的是 show 函数在虚函数表中的偏移
printf("adj of pvirtual = 0x%08lx\n", temp[1]);
// 输出结果
sizeof(pvirtual) = 16
ptr of pvirtual = 0x00000001
adj of pvirtual = 0x00000000
从代码和注释应该不需要我多解释了。
对于 adj 域的用处,实际上需要在多重继承中才会体现,由于篇幅关系,在此不作讨论,如需要了解可以和我联系,希望此文对你理解成员指针有所帮助。