首页 > 其他分享 >析构函数虚表指针回填问题

析构函数虚表指针回填问题

时间:2023-07-24 22:36:05浏览次数:34  
标签:调用 虚表 析构 void 回填 func public 函数

1 问题提出

笔者偶然发现对于含有虚函数的类,析构函数也会更新虚表指针。小有所得,特此记录。

这里使用vs2022 32位debug作为实验环境。

对于一个有虚函数的类,编译器在生成构造函数时,不只生成我们自己写的虚构函数里面的语句,还会把虚表地址赋值到对象中。

比如如下类,构造函数里面根本没有一句自己写的语句,但是编译器却自动生成了将虚表地址填充到类对象里面,这是大家都知道的。

class A {
public:
	virtual void func() {}
	A() {}
	~A() {}
};

image-20230724213316492

但是,对于析构函数,由于也是空的,生成代码和构造函数一模一样。这里再次把虚表地址填充到类对象中,对此,起初笔者觉得没有必要。想法也很简单,对象都快没有了,还把虚表地址放到对象里面,简直就是多此一举!不过,显然,编译器是对的。

image-20230724213713990

2 探索

可以猜测,这里更新虚表指针必然要和调用虚函数有些关系,虚表就是为了调用虚函数服务的嘛。

如下例子,使用两种方式调用虚函数,第一种编译之后直接给出相对偏移,也就是编译器已经确定对象a是就是类A的实例,调用func必然是调用类A的func,于是编译时就确定了调用函数的地址。但是第二种用指针调用func,由于父类指针可以指向子类对象,那么编译器就无法知道p指向的是父类对象还是子类对象,于是就从虚表中得到了func的地址。

class A {
public:
	virtual void func() {}
	A() {}
	~A() {}
};

int main() {
	A a;
	A* p = new A;

	a.func();
	p->func();
}

image-20230724215114915

既然这样的话,合理推测,如果析构函数中调用了虚函数的话,需要从虚表中得到函数地址。而对于虚基类对象和派生类对象,虚表是不同的。对于派生类来说,如果基类析构函数没有更新虚表的话,并且基类调用了某个被重写的虚函数,如果不更新虚表指针,那调用的将会是子类函数,多么糟糕!

于是笔者写下以下代码测试,在析构A时,调用虚函数func,这样如果~A不更新虚表,就会调用子类的func。不过很不幸,编译器很聪明,知道析构函数调用自己类的虚函数,将调用func优化为使用地址调用,而不是从虚表中找到func地址然后调用。

class A {
public:
    virtual void func()
    {
    }
    virtual ~A()
    {
        func();
    }
};

class B:public A{
public:
    virtual void func() { }
};
int main(){
    A* p = new B;
    delete p;
}

image-20230724220456033

既然如此,那就不让编译器知道,于是改写代码如下,这样,依然通过指针调用,编译器就无法优化了。

class A {
    friend void foobar(A *const p);
public:
    virtual void func()
    {
    }
    virtual ~A()
    {
        foobar(this);
    }
};

void foobar(A* const p)
{
    p->func();
}

class B:public A
{
public:
    virtual void func() override { }
};
int main()
{
    A* p = new B;
    delete p;
}

image-20230724221045949

结果很好,如果对象虚表指针没有更新,那么调用的func对象将是子类的。

既然如此,虽然结论已经出来了,这里也验证一下,口说无凭嘛。

class A {
    friend void foobar(A *const p);
public:
    virtual void func()
    {
        cout << "hello" << endl;
    }
    virtual ~A()
    {
        foobar(this);
    }
};

void foobar(A* const p)
{
    p->func();
}

class B:public A
{
public:
    virtual void func() override { 
        cout << "world" << endl;
    }
};
int main()
{
    A* p = new B;
    delete p;
}

对照之前代码,我将~A更新虚表的指令换成nop,这样就对象的虚表就不会更新了,将会调用子类的func。打印出world而不是hello。perfect

image-20230724221412165

image-20230724221537543

标签:调用,虚表,析构,void,回填,func,public,函数
From: https://www.cnblogs.com/zz89/p/17578527.html

相关文章

  • uniapp 省市区 回填
    <template> <view> <u-button@click="tesx">11</u-button> {{aa}} <cityv-model="aa"></city> </view></template><script> importcityfrom'./city.vue' exportdefau......
  • c++内存分布之虚析构函数
    关于本文代码演示环境:VS2017+32程序虚析构函数是一种特殊的虚函数,可以知道,虚函数影响的内存分布规律应该也适用虚析构函数。看看实际结果。Note,一个类中,虚析构函数只能有一个。本文将展开单一继承和多继承两种情况结论1.虚函数表指针和虚函数表1.1影响虚函数表指......
  • c++中虚析构函数如何实现多态的、内存布局如何?
    作者:冯Jungle链接:https://www.zhihu.com/question/36193367/answer/2242824055来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。之前Jungle写过一篇文章《探究C++:虚函数表究竟怎么回事?》,主要通过测试代码来验证虚函数表的存在,进而说明C++的多态机制......
  • VC和GCC静态变量析构顺序不同
    VC和GCC静态变量析构顺序不同(金庆的专栏)静态变量析构顺序正常情况下是构造的反序。但是VC对DLL中的静态变量好像是需等待DLL卸载时再析构,表现为主程序中的静态变量先析构,DLL中的静态变量后析构。VC测试版本为VC2010Express.例如:classA{};sta......
  • 构造和析构的bug
    目录构造析构构造析构父类一定要用virtual的析构函数dtorFa类的~Fa::Fa()没有加virtual,delete就不会调用子类的析构Fa*ptr=newSon,如果deleteptr,就执行的是父类的dtor,而不是子类的析构dtor......
  • 0001-虚函数和虚表笔记
    目录一个空对象至少占用1字节的空间展开查看:原因是在栈上分配2个对象时,要区分地址classObject{};voidFunction(){Objecto1,o2;//需要区分o1,o2的地址}空类有虚函数,需要占用一个指针的空间,即:编译器会插入一个虚函数表指针vptr有虚函......
  • C++逆向分析——构造函数和析构函数
    构造函数与析构函数构造函数structStudent{inta;intb;Student(){printf("Look.");}voidInit(inta,intb){this->a=a;this->b=b;}};如上代码中,我们发现了存在一个函数,这个函数没有返回类型并且与结构体名称一样,那这段函数在什么时候执......
  • C/C++杂记:深入虚表结构
    1.虚表与“虚函数表”在“C/C++杂记:虚函数的实现的基本原理”一文中曾提到“虚函数表”的概念,只是为了便于理解,事实是:虚函数表并不真的独立存在,它只是虚表(virtualtable)中的一部分内容。例:从图中可已看出,虚表除了包含虚函数指针,还包含其它一些信息(如:RTTI信息、偏移值等)。顺便......
  • c++中的析构函数和纯虚函数
    析构函数:c++中当delete一个类对象时,会默认调用其析构函数,析构函数的作用就是释放对象占用的堆空间。一般基类的析构函数需写成虚函数,这是因为在多态下,我们一般用基类的指针来指向一个子类对象,若基类的虚函数未被重写,那么可能会造成内存泄漏。因此需要在子类重写基类的虚函数来......
  • 虚表指针初始化顺序
    无继承时:1、分配内存2、初始化列表之前赋值虚表指针3、列表初始化4、执行构造函数体有继承时:1、分配内存2、基类构造过程(按照无继承来)3、初始化子类虚表指针4、子类列表初始化5、执行子类构造函数体Q:虚表指针在初始化列表之前被赋值,可以放在初始化列表之后赋值吗?即顺......