文章目录
- 参考
- 虚函数
- 使用虚函数的class结构
- 相关实现
- 源码
- IDA反编译
- 子类虚表和父类虚表
- 调用函数
- 菱形继承
参考
https://showlinkroom.me/2017/08/21/C-%E9%80%86%E5%90%91%E5%88%86%E6%9E%90/
https://www.cnblogs.com/bonelee/p/17299985.html
https://xz.aliyun.com/t/5242?time__1311=n4%2BxnD07itGQ%3DD54AKDsA3xCuU%3DwQNY4D&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Fu%2F15779#toc-3
虚函数
在C++中,虚函数(virtual function)是一种特殊的成员函数,它允许在派生类中重写或覆盖基类中定义的函数,从而实现多态性(polymorphism)。虚函数的关键特征和用途可以概括如下:
-
声明与定义:
要声明一个虚函数,需要在基类的成员函数声明前加上virtual
关键字。例如:class Base { public: virtual void someFunction() { // 基类的实现 } };
派生类可以重写该函数,也可以不重写,如果重写,则在派生类中提供新的实现:
class Derived : public Base { public: void someFunction() override { // 派生类的实现 } };
-
虚函数表(VTable):
实现虚函数的机制通常涉及到虚函数表(Virtual Table,简称vtable)。每个含有虚函数的类都会有一个虚函数表,表中存储着该类所有虚函数的地址。每个对象都有一个指向相应类虚函数表的指针(称为vptr)。当通过基类指针或引用调用虚函数时,程序会根据对象的vptr查找到正确的虚函数表,从而调用实际对象类型的函数实现。 -
纯虚函数与抽象类:
如果基类中的虚函数没有提供实现,并且在声明时被= 0
初始化,那么这个函数被称为纯虚函数。含有纯虚函数的类不能实例化,只能作为基类被继承,这样的类称为抽象类。
使用虚函数的class结构
相关实现
- new的本质,实际上就是malloc+构造函数
- delete的本质就是析构函数+free
源码
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person() {
age = 0;
name = "";
};
~Person() {
};
virtual void setName(string name) = 0;
virtual void setAge(int age) = 0;
protected:
int age;
string name;
};
class Student :public Person {
public:
Student() {
age = 17;
}
void setName(string name) {
this->name = name;
}
string getName() {
return this->name;
}
void setAge(int age) {
if (age > 30 || age < 6) {
cout << "not suitable for school!" << endl;
return;
}
this->age = age;
}
int getAge() {
return this->age;
}
};
int main() {
Student* stu = new Student();
stu->setName("Link");
stu->setAge(18);
cout << stu->getName() << " with age " << stu->getAge() << endl;
return 0;
}
IDA反编译
子类构造函数
父类构造函数
子类虚表和父类虚表
纯虚函数的抽象类的虚表由__cxa_pure_virtual
填充,有几个纯虚函数就有几个__cxa_pure_virtual
,student类虚表分别有各自实现的虚函数,虚表上面是该类的typeinfo的地址
- RTTI (Run-Time Type Information) 是C++的一项特性,允许程序在运行时识别对象的类型。
typeid
运算符和dynamic_cast
都是基于RTTI实现的。- 每个包含虚函数的类实例,在内存中都有一个隐藏的指针(称为vptr,虚函数指针),指向该类的虚函数表(vtable)。这个vptr是在对象创建时由编译器自动初始化的。
- RTTI信息通常与vtable一起管理,其中可能包括类型描述信息和继承链信息等。
-
Aclass* ptra = new Bclass;
:这行代码创建了一个指向Bclass
实例的指针,但这个指针被声明为Aclass*
类型。因为Bclass
是Aclass
的子类(假设如此),所以这是向上转型,是多态的基础。 -
int ** ptrvf = (int**)(ptra);
:这里进行了一次不安全的类型转换,将Aclass
指针转换为了int**
。这种转换通常是为了直接访问虚函数指针(vptr),因为在许多系统上,vptr是对象内存布局的第一个元素,可以被视为一个指针的指针。但是,请注意,这种做法非常危险且非标准,违反了类型安全原则。 -
*((int*)ptrvf[0]-1)
:这一部分是为了找到RTTI信息的位置。ptrvf[0]
访问的是第一个虚函数(按指针算起),减1则是尝试访问vptr前的一个位置,期望那里存放着RTTI相关数据的指针。这一步同样非常危险,依赖于特定编译器和平台的实现细节。 -
*((RTTICompleteObjectLocator*)(...))
:这里进一步将找到的地址强制转换为RTTICompleteObjectLocator
指针,并解引用它。RTTICompleteObjectLocator
是一个内部使用的结构(非标准公开接口),用于存储有关对象类型的完整信息,比如类的类型描述符、偏移量到类型名称字符串等。
调用函数
对于虚函数
- 先通过对象开始八个字节得到虚表地址
- 通过虚表地址和相关偏移得到函数地址
- 调用函数
对于非虚函数 - 直接调用再text段实现的函数
菱形继承
#include<stdio.h>
#include<stdlib.h>
//间接基类
class A {
public:
virtual void function() {
printf("A virtual function\n");
}
int a;
};
//直接基类
class B :virtual public A { //虚继承
public:
virtual void func() {
printf("B virtual func()\n");
}
int b;
};
//直接基类
class C :virtual public A { //虚继承
public:
virtual void func() {
printf("C virtual func()");
}
int c;
};
//派生类
class D :public B, public C {
public:
virtual void function() {
printf("D virtual function()");
}
int d;
};
int main(int argc, char** argv) {
A* A_ptr = (A*)new D();
A_ptr->function();
return 0;
}
B和C继承A,D继承B和C
通过虚表地址减去24后的位置的内容是32,然后再加上起始地址,对应到的V4为D内的A的对象内存位置,offset vbase应该是与虚基类的偏移也就是和A的偏移
这里给各个父类对象在本对象的空间的前八个字节赋值时,用的是本对象虚表对应到的不同父类的虚函数的地址
此时对应的虚表地址,所以**两次得到D::function的地址,然后参数为对象自己