C/C++雜記:深刻理解數據成員指針、函數成員指針

1. 數據成員指針

對於普通指針變量來講,其值是它所指向的地址,0表示空指針。
而對於數據成員指針變量來講,其值是數據成員所在地址相對於對象起始地址的偏移值,空指針用-1表示。例:
html

代碼示例:git

struct X {
    int a;
    int b;
};
#define VALUE_OF_PTR(p)     (*(long*)&p)
int main() {
    int X::*p = 0;  // VALUE_OF_PTR(p) == -1
    p = &X::a;      // VALUE_OF_PTR(p) == 0
    p = &X::b;      // VALUE_OF_PTR(p) == 4
    return 0;
}
View Code

 

2. 函數成員指針

函數成員指針與普通函數指針相比,其size爲普通函數指針的兩倍(x64下爲16字節),分爲:ptr和adj兩部分。github

(1) 非虛函數成員指針ide

ptr部份內容爲函數指針(指向一個全局函數,該函數的第一個參數爲this指針),adj部分始終爲0。例:
函數

代碼示例:this

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("&B::foo->ptr: 0x%lX\n", PART1_OF_PTR(pbfoo));
    printf("&B::foo->adj: 0x%lX\n", PART2_OF_PTR(pbfoo));    // 0
    printf("&D::foo->ptr: 0x%lX\n", PART1_OF_PTR(pdfoo));
    printf("&D::foo->adj: 0x%lX\n", PART2_OF_PTR(pdfoo));    // 0
    printf("&D::bar->ptr: 0x%lX\n", PART1_OF_PTR(pdbar));
    printf("&D::bar->adj: 0x%lX\n", PART2_OF_PTR(pdbar));    // 0

    D* d = new D();
    d->foo();
    _ZN1B3fooEv(d); // equal to d->foo()
    d->bar();
    _ZN1D3barEv(d); // equal to d->bar()
    return 0;
}
View Code

 

(2) 虛函數成員指針spa

ptr部份內容爲虛函數對應的函數指針在虛函數表中的偏移地址加1(之因此加1是爲了用0表示空指針),而adj部分爲調節this指針的偏移字節數。例:
指針

說明:code

  • A和B都沒有基類,可是都有虛函數,所以各有一個虛函數指針(假設爲vptr)。
  • C同時繼承了A和B,所以會繼承兩個虛函數指針,可是爲了節省空間,C會與主基類A公用一個虛函數指針(即上圖中vptr1),繼承自B的虛函數指針假設爲vptr2。
  • C沒有重寫繼承自A和B的虛函數,所以在C的虛函數表中存在A::foo和B::bar函數指針(若是C中重寫了foo(),則C的虛函數表中A::foo會被替換爲C::foo)。
  • C中有兩個虛函數指針vptr1和vptr2,至關於有兩張虛函數表。
  • A::foo(C::foo)、B::Bar(C::bar)都在虛函數表中偏移地址爲0的位置,所以ptr爲1(0+1=1)。而C::quz在偏移爲8的位置,所以ptr爲9(8+1=9)。
  • 當咱們使用pc調用C::bar()時,如:「(pc->*pcbar)()」,實際上調用的是B::bar()(即_ZN1B3barEv(pc)),pc須要被轉換爲B*類型指針,所以須要對this指針進行調節(調節至pb指向的地址),所以adj爲8。

代碼示例:htm

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::foo->adj: 0x%lX\n", PART2_OF_PTR(pcfoo));   // 0
    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;
}
View Code

 

參考:

C++ ABI for Itanium: 2.3 Member Pointers

相關文章
相關標籤/搜索