深入理解c++多态实现原理

8 篇文章 0 订阅

引言

       多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。与之相对应的编译时绑定函数称为静态绑定。多态是面向对象编程的核心思想之一,因此我们有必要深入探索一下它的实现原理。理解了原理才能更好的使用。

前置条件

        现有代码如下所示,非常简单的例子。通过基类的引用调用recv函数来触发多态。接下来的分析涉及汇编知识,如果还没熟悉汇编,可以看另外一篇文章<深入理解c++函数调用的参数传递与局部变量申请>

#include <stdio.h>
#include <string>

#define trace(fmt, ...) printf("[trace] %s:%s:%d " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)

class IClient
{
	public:
		IClient(){};
		virtual ~IClient(){};
		virtual ssize_t recv(char *buff, size_t len) = 0;
};
class CStreamClient: public IClient
{
	public:
		CStreamClient(){};
		~CStreamClient(){};
		ssize_t recv(char *buff, size_t len)
		{
			trace("recv %d bytes in %p\n", len, buff);
			return len;
		}
};
int main(int argc, char **argv)
{
	CStreamClient streamclient;
	IClient &client = streamclient;
	client.recv(NULL, 0);
	return 0;
}
分析

        我们都知道拥有虚函数的类都有属于自己的虚表存放在.text段中,实例化后对象拥有一个内建变量_vptr虚表指针,它指向了实际对象的虚表。那么这个_vptr在哪里初始化呢?当然是构造函数。如下图所示,CStreamClient与IClient都会在自己的构造函数中初始化_vptr为自己的虚表地址。不过父类构造先于子类构造函数执行,因此CStreamClient对象指向的是自己的虚表。


        接下来我们对main反汇编代码分析如下所示,client对象处于栈中。首先计算出client对象的地址。接下来解引用得到_vptr的值。值得注意的是_vptr并非指向虚表起始地址,而是+0X08。起始4字节猜测是保留,毕竟全是0x00;接下来4字节存放的是对象的类型信息,反汇编typeid()函数你就会发现它就是在读取这个地址指向的typeinfo。

        得到_vptr地址后+0x08的偏移得到虚表存放recv地址的地址,再解引用就得到实际绑定对象的recv函数地址了。接下来是各个参数入栈,再跳转执行。


        那么,这就结束了吗?眼尖的你肯定发现了,为什么会有两个的析构函数?函数声明还是一个模子出来的。那么我们对这两个析构反汇编分析对比一下有什么不同。有注意到什么不同吗?

        左侧的析构函数赋值eax为零,eax分为[16位ax|8位ah|8位al]。那么test指令将会置位ZF标志位,因此接下来的je指令将会跳转,也就是不进行free的操作。右侧的析构函数在调用左侧析构的基础上进行了释放内存。那么就是说它们分别是为栈上和堆上的对象准备的。


  • 4
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值