【C++】C++中打印虚函数表内容
·
【C++】C++中打印虚函数表内容
一、虚函数的简介
虚函数是 C++ 中实现多态性的重要机制之一。多态性是面向对象编程(OOP)的核心概念之一,它允许程序在执行时根据对象的实际类型调用相应的方法。以下是虚函数与多态性之间关系的详细解释。
多态性的定义
多态性(Polymorphism)允许对象以多种形式出现。在面向对象编程中,主要有两种类型的多态性:
-
编译时多态性(静态多态性):
- 通过函数重载和运算符重载实现。在编译时决定调用哪个函数。
-
运行时多态性(动态多态性):
- 通过虚函数和继承实现。在运行时根据对象的实际类型决定调用哪个函数。
二、虚函数的作用
虚函数 是在基类中声明并在派生类中重写的成员函数。通过将函数声明为虚函数,您可以在基类指针或引用调用该函数时,确保调用的是派生类中实现的函数。
当您通过基类指针或引用调用虚函数时,程序会查找该对象的虚表(vtable)(虚函数指针的数组),并根据对象的实际类型(而不是指针或引用的类型)来调用相应的方法。这种机制就是运行时多态性的体现。但是 分配的时候 虚表是在编译的时候确定的 ,虚表指针是在运行时分配的。
三、虚表(vtable)
虚表(vtable)是在编译时确定的,但实际的虚表指针(vptr)是在运行时分配的。让我们详细分析这两个概念及其在程序中的作用。
-
定义:
-
虚表是一个包含指向虚函数实现的指针数组。每个包含虚函数的类都有一个对应的虚表。
-
当编译器编译类时,它会为每个类生成一个虚表,并在表中存储该类的虚函数的地址。
-
-
编译时确定:
- 虚表的结构是在编译时确定的,包括它的内容(即虚函数的地址)。因此,编译器能够知道每个类的虚表在运行时的布局。
-
单个类的虚表:
- 如果一个类没有虚函数,它将没有虚表。
- 如果一个类继承了虚函数,编译器会在类的定义中为该类生成虚表。
四、虚指针(vptr)
-
定义:
- 每个对象(实例)都包含一个指向其虚表的指针,称为虚指针(vptr)。这个指针指向对应类的虚表。
-
运行时分配:
- 当对象被创建时,运行时系统会分配内存,并为对象的 vptr 指针指向相应的虚表。
- 例如,当创建派生类的对象时,vptr 会指向派生类的虚表,这样可以确保调用到正确的虚函数实现。
运行时的虚表使用
当通过基类指针或引用调用虚函数时,程序会使用 vptr 来查找实际的虚表,从而找到并调用正确的函数实现。这种机制允许实现多态性。
六、简单总结
- 虚表:在编译时生成,定义了类的虚函数的实现地址。
- 虚指针:在运行时为每个对象分配,指向该对象对应的虚表。
示例代码
以下是一个简单的示例,展示了如何使用虚函数实现多态性:
class B1
{
public:
virtual void Test1()
{
std::cout << "B1 test1" << std::endl;
}
virtual void Test2()
{
std::cout << "B1 test2" << std::endl;
}
int b1 = 100;
};
class A : public B1
{
public:
void Test1() override
{
std::cout << "A test1" << std::endl;
}
void Test2() override
{
std::cout << "A test2" << std::endl;
}
};
class C : public B1
{
void Test1() override
{
std::cout << "C test1" << std::endl;
}
void Test2() override
{
std::cout << "C test2" << std::endl;
}
};
void TestClass(B1 *b)
{
std::cout << "in TestClass" << std::endl;
b->Test1();
b->Test2();
}
int main(int argc, char *argv[])
{
{
B1 b;
b.Test1();
b.Test2();
A a;
a.Test1();
a.Test2();
TestClass(&a);
C c;
TestClass(&c);
}
std::cout << "============================" << std::endl;
return 0;
}
输出:
B1 test1
B1 test2
A test1
A test2
in TestClass
A test1
A test2
in TestClass
C test1
C test2
============================
七、多继承虚表的内存布局
/// 虚函数表分析
class B1
{
public:
virtual void Test1()
{
std::cout << "B1 test1" << std::endl;
}
virtual void Test2()
{
std::cout << "B1 test2" << std::endl;
}
int b1 = 100;
};
class B2
{
public:
virtual void test3()
{
std::cout << "B2 test3" << std::endl;
}
virtual void test4()
{
std::cout << "B2 test4" << std::endl;
}
int b2 = 200;
};
class A : public B1
{
public:
void Test1() override
{
std::cout << "A test1" << std::endl;
}
void Test2() override
{
std::cout << "A test2" << std::endl;
}
};
class C : public B1
{
void Test1() override
{
std::cout << "C test1" << std::endl;
}
void Test2() override
{
std::cout << "C test2" << std::endl;
}
};
void TestClass(B1 *b)
{
std::cout << "in TestClass" << std::endl;
b->Test1();
b->Test2();
}
class D : public B1, public B2
{
public:
void Test1() override
{
std::cout << "D Test1" << std::endl;
}
void Test2() override
{
std::cout << "D Test2" << std::endl;
}
void test3() override
{
std::cout << "D test3" << std::endl;
}
void test4() override
{
std::cout << "D test4" << std::endl;
}
int a = 100;
};
求 对象 d1 的大小
int main(int argc, char *argv[])
{
std::cout << "cppds.com" << std::endl;
{
B1 b;
b.Test1();
b.Test2();
A a;
a.Test1();
a.Test2();
TestClass(&a);
C c;
TestClass(&c);
}
std::cout << "============================" << std::endl;
/// 多继承各个虚表的获取
{
typedef void (**vptr)(); /// 指向虚指针数组的指针
D d1;
std::cout << "&d1 = " << reinterpret_cast<unsigned long long>(&d1) << std::endl;
vptr *vptr1 = reinterpret_cast<vptr *>(&d1); ///
std::cout << "&(d1.b1) = " << reinterpret_cast<unsigned long long>(&d1.b1)
<< std::endl; /// 首地址 + 8, 虚表指针就是8字节的 所以加8
std::cout << "vptr1 = " << reinterpret_cast<unsigned long long>(vptr1) << std::endl; /// 首地址
auto test1 = **vptr1;
test1();
std::cout << " test1 = " << reinterpret_cast<unsigned long long>(test1) << std::endl;
auto test2 = *(*vptr1 + 1);
test2();
std::cout << " test2 = " << reinterpret_cast<unsigned long long>(test2) << std::endl;
vptr *vptr2 = reinterpret_cast<vptr *>(&d1) + 2; /// 中间隔开了一个变量 8B一格
std::cout << "vptr2 = " << reinterpret_cast<unsigned long long>(vptr2)
<< std::endl; /// B2的虚表指针 /// 内存对齐 + 8
(**vptr2)();
(*(*vptr2 + 1))();
std::cout << "&(d1.b2) = " << reinterpret_cast<unsigned long long>(&d1.b2) << std::endl;
std::cout << "end = " << reinterpret_cast<unsigned long long>(&d1.b2) + 8 << std::endl;
/// + 8 多一个变量
std::cout << "sizeof(D) = " << sizeof(D) << std::endl; /// 40
}
getchar();
return 0;
}
输出:
cppds.com
B1 test1
B1 test2
A test1
A test2
in TestClass
A test1
A test2
in TestClass
C test1
C test2
============================
&d1 = 650109777272
&(d1.b1) = 650109777280
vptr1 = 650109777272
D Test1
test1 = 140702540239033
D Test2
test2 = 140702540238958
vptr2 = 650109777288
D test3
D test4
&(d1.b2) = 650109777296
end = 650109777304
sizeof(D) = 40
更多推荐

所有评论(0)