[Itanium C++ ABI] 成员指针

总结

  • 数据成员指针,其值是数据成员相对于对象起始地址的偏移,空指针用-1表示;
  • 非虚函数成员指针,ptr部分内容为函数指针,指向一个全局函数,该函数的第一个参数为this指针;adj部分始终为0;
  • 虚成员函数指针,ptr部分内容为虚函数对应的函数指针在虚函数表中的偏移地址加1;adj部分为调节this指针的偏移,目的是指向相应的虚函数表;

数据成员指针

对于普通指针变量来说,其值是它所指向的地址,0表示空指针;
对于数据成员指针变量来说,其值是数据成员相对于对象起始地址的偏移,空指针用-1表示;

struct X {
    int a;
    int b;
};

#define VALUE_OF_PTR(p)     (*(long*)&p)

int main() {
    int X::*p = 0;
    cout << VALUE_OF_PTR(p) << endl;

    p = &X::a;
    cout << VALUE_OF_PTR(p) << endl;

    p = &X::b;
    cout << VALUE_OF_PTR(p) << endl;

    return 0;
}

函数成员指针

其size为普通函数指针的两倍(x64下为16字节),分为:ptr和adj两部分;

  • 非虚函数成员指针

ptr部分内容为函数指针(指向一个全局函数,该函数的第一个参数为this指针),adj部分始终为0

extern "C" int printf(const char*, ...);

struct B {
    void foo() {  printf("B::foo(): this = 0x%p\n", this); }
};

struct D : public B {
    void bar() { printf("D::bar(): this = 0x%p\n", this); }
};

void (B::*pbfoo)() = &B::foo; // ptr: points to _ZN1B3fooEv, adj: 0
void (D::*pdfoo)() = &D::foo; // ptr: points to _ZN1B3fooEv, adj: 0
void (D::*pdbar)() = &D::bar; // ptr: points to _ZN1D3barEv, adj: 0

extern "C" void _ZN1B3fooEv(B*);
extern "C" void _ZN1D3barEv(D*);

#define PART1_OF_PTR(p)     (((long*)&p)[0])
#define PART2_OF_PTR(p)     (((long*)&p)[1])

int main() {
    printf("%p\n", _ZN1B3fooEv);
    printf("%p\n\n", _ZN1D3barEv);

    printf("&B::foo->ptr: 0x%lx\n", PART1_OF_PTR(pbfoo));
    printf("&B::foo->adj: 0x%lx\n", PART2_OF_PTR(pbfoo));

    printf("&D::foo->ptr: 0x%lx\n", PART1_OF_PTR(pdfoo));
    printf("&D::foo->adj: 0x%lx\n", PART2_OF_PTR(pdfoo));

    printf("&D::bar->ptr: 0x%lx\n", PART1_OF_PTR(pdbar));
    printf("&D::bar->adj: 0x%lx\n\n", PART2_OF_PTR(pdbar));


    D* d = new D();
    printf("%p\n", (long *)d);

    d->foo();
    _ZN1B3fooEv(d); // equal to d->foo()

    d->bar();
    _ZN1D3barEv(d); // equal to d->bar()

    return 0;
}

  • 函数成员指针

ptr部分内容为虚函数对应的函数指针在虚函数表中的偏移地址加1
adj部分为调节this指针的偏移字节数;

extern "C" int printf(const char*, ...);

struct A {
    virtual void foo() { printf("A::foo(): this = 0x%p\n", this); }
};
struct B {
    virtual void bar() { printf("B::bar(): this = 0x%p\n", this); }
};
struct C : public A, public B {
    virtual void quz() { printf("C::quz(): this = 0x%p\n", this); }
};

void (A::*pafoo)() = &A::foo;   // ptr: 1, adj: 0

void (B::*pbbar)() = &B::bar;   // ptr: 1, adj: 0

void (C::*pcfoo)() = &C::foo;   // ptr: 1, adj: 0
void (C::*pcquz)() = &C::quz;   // ptr: 9, adj: 0
void (C::*pcbar)() = &C::bar;   // ptr: 1, adj: 8

#define PART1_OF_PTR(p)     (((long*)&p)[0])
#define PART2_OF_PTR(p)     (((long*)&p)[1])

int main() {
    printf("&A::foo->ptr: 0x%lX, ", PART1_OF_PTR(pafoo));   // 1
    printf("&A::foo->adj: 0x%lX\n", PART2_OF_PTR(pafoo));   // 0

    printf("&B::bar->ptr: 0x%lX, ", PART1_OF_PTR(pbbar));   // 1
    printf("&B::bar->adj: 0x%lX\n", PART2_OF_PTR(pbbar));   // 0

    printf("&C::foo->ptr: 0x%lX, ", PART1_OF_PTR(pcfoo));   // 1

    printf("&C::quz->ptr: 0x%lX, ", PART1_OF_PTR(pcquz));   // 9
    printf("&C::quz->adj: 0x%lX\n", PART2_OF_PTR(pcquz));   // 0

    printf("&C::bar->ptr: 0x%lX, ", PART1_OF_PTR(pcbar));   // 1
    printf("&C::bar->adj: 0x%lX\n", PART2_OF_PTR(pcbar));   // 8
    return 0;
}
  • A和B都没有基类,但是都有虚函数,因此各有一个虚函数指针(以及虚函数表);
  • C同时继承了A和B,因此会有两个虚函数指针(以及两张虚函数表);
  • C本身也有虚函数,但是为了节省空间,C会与主基类A公用一个虚函数指针、虚函数表;
  • C没有重写继承自A和B的虚函数,因此在C的虚函数表中存在A::foo和B::bar函数指针;
  • 如果C中重写了foo(),则C的虚函数表中A::foo会被替换为C::foo;(因此,C的虚函数表是单独分配的,而非共用)
  • A::foo(C::foo)、B::Bar(C::bar)都在虚函数表中偏移地址为0的位置,因此ptr为1(0+1=1),之所以加1是为了用0表示空指针;
  • C::quz在偏移为8的位置,因此ptr为9(8+1=9);
  • 当我们使用pc调用C::bar()时,如:“(pc->*pcbar)()”,实际上调用的是B::bar()(即_ZN1B3barEv(pc))
    • pc指向的C中包含两个虚函数指针,其中第一个是来自A的虚函数表指针;
    • 为了调用B中的虚函数,就必须知道B的虚函数表;
    • B的虚函数表指针距离pc指向的起始位置为8,这也就是为什么adj == 8;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值