IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++虚表与ABI兼容 -> 正文阅读

[C++知识库]C++虚表与ABI兼容

虚表生成

C++的类只要有一个虚函数,就会生成一张虚表

class A
{
};

class B
{
public:
	virtual void vfunc1();
}
sizeof(A) = 1	// 空类1个字节用于地址定位
sizeof(B) = 4	// 有虚表指针,占sizeof(void*)字节

子类的虚表

VisualStudio可以使用 cl Test.cpp -d1 reportSingleClassLayoutC 查看内存布局
reportSingleClassLayout<类名>,例如 reportSingleClassLayoutC,是查看类C的布局

class A
{
public:
	virtual void vfunc1();
private:
	int a;
};

class B
{
public:
	virtual void vfunc2();
private:
	int b;
};

class C1 : public A
{
public:
    virtual void vfunc3();
private:
    int c;
};

class C2 : public A, public B
{
public:
    virtual void vfunc3();
private:
    int c;
};

C1 的内存布局

class C1        size(12):
        +---
 0      | +--- (base class A)
 0      | | {vfptr}
 4      | | a
        | +---
 8      | c
        +---

C1::$vftable@:
        | &C1_meta
        |  0
 0      | &A::vfunc1
 1      | &C1::vfunc3

C2 的内存布局

class C2        size(20):
        +---
 0      | +--- (base class A)
 0      | | {vfptr}
 4      | | a
        | +---
 8      | +--- (base class B)
 8      | | {vfptr}
12      | | b
        | +---
16      | c
        +---

C2::$vftable@A@:
        | &C2_meta
        |  0
 0      | &A::vfunc1
 1      | &C2::vfunc3

C2::$vftable@B@:
        | -8
 0      | &B::vfunc2

C++类虚表中函数顺序规则

  1. 从基类开始,按照申明顺序每遇到一个不是重写的虚函数,就记录在表中
  2. 如果有重载,则提前重载的虚函数
  3. 依次循环遍历子类,如果遇到重写,则替换相应的虚函数

举例

class A
{
public:
	virtual void vfunc1() = 0;
	virtual void vfunc2() = 0;
	virtual void vfunc1(int x) = 0;
	virtual void vfunc3() = 0;
	virtual void vfunc1(int x, int y) = 0;
};

class B : public A
{
public:
	virtual void vfunc1(int x) = 0;
	virtual void vfunc4() = 0;
	virtual void vfunc2(int x) = 0
}

请问B的虚表是应该是什么样的?

  1. 遍历A中的虚函数
void A::vfunc1();

由于 vfunc1 有两个重载,按照第 2 条规则,依次提前重载函数

void A::vfunc1();
void A::vfunc1(int x);
void A::vfunc1(int x, int y);
  1. 继续遍历A中的虚函数
void A::vfunc1();
void A::vfunc1(int x);
void A::vfunc1(int x, int y);
void A::vfunc2();
void A::vfunc3();
  1. 由于 B 重写了 Avoid vfunc1(int x) 函数,所以将表中对应的函数替换
void A::vfunc1();
void B::vfunc1(int x);
void A::vfunc1(int x, int y);
void A::vfunc2();
void A::vfunc3();
  1. 添加 B::vfunc4() 到虚表中
void A::vfunc1();
void B::vfunc1(int x);
void A::vfunc1(int x, int y);
void A::vfunc2();
void A::vfunc3();
void B::vfunc4();
  1. 由于 B::vfunc2(int x) 没有重写A中的函数,按照规则 1 添加到虚表中
void A::vfunc1();
void B::vfunc1(int x);
void A::vfunc1(int x, int y);
void A::vfunc2();
void A::vfunc3();
void B::vfunc4();
void B::vfunc2(int x);

搞清楚虚表有什么用?

答:ABI兼容

举例

你写了这样一个SDK

  • awesome.h
class IAwesomeSDK
{
public:
	virtual void foo() = 0;
	virtual void bar(int x) = 0;
};

extern "C" {

// 创建SDK实例
IAwesomeSDK *createAwesomeInstance();

// 销毁SDK实例
void destroyAwesomeInstance();

} // extern "C"

调用方使用时一般这么写

  • demo.cpp
int main(int argc, char **argv)
{
	IAwesomeSDK *sdk = createAwesomeInstance();
	sdk->foo();
	sdk->bar();
	destroyAwesomeInstance();
	return 0;
}

如果保证新发布的动态库可以兼容之前的程序(集成DLL的程序不需要重新编译,就可以使用新DLL),那么动态库中添加功能需要注意

  1. 只能在类最后添加新函数
class IAwesomeSDK
{
public:
	virtual void feature1() = 0;		// 错误
	virtual void foo() = 0;
	virtual void bar(int x) = 0;
};
  1. 添加的新函数可以与旧函数重名(重载)
class IAwesomeSDK
{
public:
	virtual void foo() = 0;
	virtual void bar(int x) = 0;
	virtual void bar() = 0;				// 错误
};
  1. 可以修改旧函数的签名(参数,返回值,限定符等)
class IAwesomeSDK
{
public:
	virtual void foo(int x = 0) = 0;	// 错误
	virtual void bar(int x) = 0;
};
  1. 可以重新排序旧函数
class IAwesomeSDK
{
public:
	virtual void bar(int x) = 0;		// 错误
	virtual void foo() = 0;				// 错误
};

这时你要添加一个新功能,还希望旧程序可以不重新编译替换新DLL,你可以这么做

class IAwesomeSDK
{
public:
	virtual void foo() = 0;
	virtual void bar(int x) = 0;
	virtual void feature() = 0;			// 正确
};

导出DLL注意事项

  1. 申请和释放保持在同一模块

最好不要在接口处使用STL库,除非编译器选项一致、STL实现一致、系统平台一致

class IAwesomeSDK
{
public:
	virtual void foo() = 0;
	virtual void bar(int x) = 0;
	virtual std::string feature() = 0;			// 错误,模块内申请,模块外释放
};
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-05 23:22:06  更:2022-07-05 23:24:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:50:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码