C/C++杂记:深入理解数据成员指针、函数成员指针

1. 数据成员指针

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

代码示例:

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;
}

2. 函数成员指针

函数成员指针与普通函数指针相比,其size为普通函数指针的两倍(x64下为16字节),分为:ptr和adj两部分。

(1) 非虚函数成员指针

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("&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;
}

(2) 虚函数成员指针

ptr部分内容为虚函数对应的函数指针在虚函数表中的偏移地址加1(之所以加1是为了用0表示空指针),而adj部分为调节this指针的偏移字节数。例:

说明:

  • 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。

代码示例:

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;
}

参考:

C++ ABI for Itanium: 2.3 Member Pointers

时间: 2024-08-04 03:36:37

C/C++杂记:深入理解数据成员指针、函数成员指针的相关文章

关于指针函数与指针数组的若干问题归纳

1.C语言中计算数组的占用内存及数组元素个数的语句 int nums[4] = {2,7,12,13}; int numsSize = sizeof(nums); int numsCount  =  sizeof(nums)/sizeof(int); 2.C语言中如何返回一个数组 在C语言中,无法直接返回一个数组,可通过返回对应类型指针的方式返回数组. #include<stdio.h>#include<stdlib.h> int* twoSum(int* nums, int nu

【转载】C/C++杂记:深入理解数据成员指针、函数成员指针

原文:C/C++杂记:深入理解数据成员指针.函数成员指针 1. 数据成员指针 对于普通指针变量来说,其值是它所指向的地址,0表示空指针.而对于数据成员指针变量来说,其值是数据成员所在地址相对于对象起始地址的偏移值,空指针用-1表示.例: 代码示例:   2. 函数成员指针 函数成员指针与普通函数指针相比,其size为普通函数指针的两倍(x64下为16字节),分为:ptr和adj两部分. (1) 非虚函数成员指针 ptr部分内容为函数指针(指向一个全局函数,该函数的第一个参数为this指针),ad

深入理解 [指针函数] 、[函数指针]、[指针的指针]、[指向指针数组的指针]

指针函数 1.指针函数是指带指针的函数,即本质是一个函数.当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中. 函数返回类型是某一类型的指针: 格式: 类型标识符  *函数名(参数表) int *match(void *key_x,void *key_y); 解析:首先来说它是一个函数,只不过这个函数的返回值是一个地址值.函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针

(转)指针函数与函数指针的区别

一.在学习C过程中发现这"指针函数"与"函数指针"容易搞错. 1.指针函数是指带指针的函数,即本质是一个函数.函数返回类型是某一类型的指针 类型标识符    *函数名(参数表) int *f(x,y); 首先它是一个函数,只不过这个函数的返回值是一个地址值.函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量. 表示: float *fun(); float *p; p = fun(a);

指针数组,数组指针、指针函数,函数指针

听到这几个名词不知道大家什么感受,反正我是一脸懵逼,不过我还是比较好学的,在老师的指导下,自己下去也钻研了一下,有一些自己的见解,我想在学习过程中也有童鞋遇到了相同的问题,希望我的总结能给你带来帮助. 首先我们想明确一下这几个概念,知道他们都表示什么,ok,请看: 函数指针:函数指针是指向函数的指针变量. 因而"函数指针"本身首先应是指针变量,只不过该指针变量指向函数.这正如用指针变量可指向整型变量.字符型.数组一样,这里是指向函数.如前所述,C在编译时,每一个函数都有一个入口地址,该

指针数组、数组指针、函数指针、指针函数总结

指针数组 && 数组指针 char *a[5]; //定义一个指针数组, 数组的元素都是char *指针类型.初始化也可以在里面存放字符或字符串.a的类型是char *[5] //若char *a[5]={"red","white","blue","dark","green"}; //a在这里的类型是char *[],实质上是一个二级指针.也就是说a所代表的那块内存里面存放着的是数组中第一个

指针数组、数组指针、函数指针、指针函数总结C++

指针数组.数组指针.函数指针.指针函数总结 2014-11-11 指针数组 && 数组指针 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 char *a[5];      //定义一个指针数组, 数组的元素都是char *指针类型.初始化也可以在里面存放字符或字符串.a的类型是char *[5] //若char *a[5]={"red","white","blue","dark",&qu

C语言基础知识----指针数组 &amp;&amp; 数组指针 &amp;&amp; 函数指针 &amp;&amp;指针函数

指针数组 && 数组指针 char (*ptr)[5]; //定义一个指向数组指针ptr,指向包含5个char类型的数组 char *a[5]; //定义一个指针数组a,包含5个char*类型指针 #include <stdio.h> int main(void) {     char *a[5]={"red","white","blue","dark","green"};   

分清函数指针和指针函数_

关于指针和数组斩不断理还乱的恩怨还真是说了不少,不过现在应该已经理清了.有了上一讲的基础,本讲的内容相对来说就比较容易理解了. 1.指向函数的指针(函数指针) 来分析这样一个声明,void (*f) ( ); 虽然()的优先级高于*,但由于有括号存在,首先执行的是解引用,所以f是一个指针:接下来执行( ),表明f指向一个函数,这个函数不返回任何值.现在得出结论:f是一个指向不接受参数且不返回任何值的函数的指针,简称函数指针(pointer to function). 对比一下int(*p) [1