CPU眼里的 class vs struct

转自微信公众号《阿布编程》
“我们能用C语言实现C++的:继承、成员函数、虚函数吗?不仅可以,而且还一摸一样!”

01

提出问题

说到C和C++的差别,大家很容易联想到面向对象和面向过程的差异。毕竟类,也就是class,几乎是所有面向对象设计的标准配置。

当然,也有同学表示不服,C语言里面也有struct呀,很多class能作的事情,用struct也可以完成。如果真是这样的话,为什么很少看见有人用C语言,作面向对象的程序设计呢?

02

基本分析

这里,就让我们一起用CPU的视角,从它们的实现逻辑上,回答这些问题吧。打开Compiler Explorer写一段代码,需要注意的是为了抵消字节对齐,所产生的冗余空间,针对当前的64位编译环境,我们会统一把各种数据结构,定义成8字节的long类型;为了抵消编译器对访问权限的控制,我们会把class的成员变量和成员函数统一设置成:public。

这里,我们先在C++编译环境中定义一个简单的类A,里面有两个成员变量x和y;然后在C编译环境中定义一个struct B,里面也有两个成员变量x和y;最后分别写一个main函数,分别作一下两个成员变量的写操作:

图片

老规矩,不用理会每条CPU指令的具体涵义,我们只对比二者的差异,如你所见,除了变量a、b的内存地址不同外,二者对应的CPU指令完全一致。

而且对象a和结构体b所占的内存空间都是16个字节,正好是两个long类型的数据长度,这看上去非常合理。所以,我们可以确定对象a和变量b,在内存上的空间分布是完全一致的:

图片

03

继承分析

但这还远远不够,因为继承才是class最显著的特征。让我们定义一个类A的派生类AA;同时,也照猫画虎的定义一个派生的结构体BB;最后,对新的成员变量z,作一下写操作:

图片

不出意外,二者对应的CPU指令,仍然是一致的。对象aa和结构体bb所占据的内存空间都同样为新增的变量z,增加了8个字节的存储空间,达到了24个字节:

图片

因为它们在内存空间上的分布完全一致,所以,此时class专属的继承特性,在struct身上,也能窥探一二,甚至还更加直接、清晰。

04

成员函数分析

但class的高级之处,不仅在于可以继承和封装数据,还可以封装方法,也就是成员函数。而由于C语言的语法规则限制,我们一般不能在struct内部定义成员函数,也正是这个原因,我们也常说struct是“面向数据”的。好了,让我们为类A增加一个成员函数func:

图片

你发现了吗?我们虽然增加了一个成员函数func,但对象a所占据的内存空间并没有变化,还是2个成员变量a、b的容量大小,还是只占据16个字节。其实,无论在class内部定义多少个成员函数,都不会影响对象a的大小和内存分布。如果这样的话,成员函数跟定义在class外部的普通函数,就没有本质区别。

是的,让我们也为struct B也写一个“成员函数”func吧:

图片

如果对比一上面2张图中的CPU指令,你会发现:struct B的“成员函数”func跟class A的成员函数func,对应的CPU指令完全相同!而且其函数的调用部分main函数对应的CPU指令,也是完全相同的。

相信如果看过“CPU眼里的:this指针”的话,对这个结果一定不意外。所以,用struct也可以实现class的成员函数和this指针,且效果相同。

05

虚函数分析

好吧,哪又怎么样呢?难道你还能用struct实现虚函数吗?让我们先在类A的成员函数前加上关键字virtual,让它变成一个虚函数。而struct B的“成员函数”则不需要作任何调整,如“CPU眼里的:虚函数”所说,虚函数体的实现,跟普通成员函数体,没有本质区别。

所以,问题的关键在于,我们如何为struct实现“动态绑定”的调用方式,为了简化代码,我们先typedef一个函数指针类型,一个大写的FUNC,

看过“CPU眼里的:虚函数”的同学,还记得虚函数的实现机制吗?编译器会偷偷的为class添加一个隐形的指针变量v,用来记录虚函数表的首地址。

同样的方法,我们也为struct B定义虚函数表,由于struct B没有可以自动调用的构造函数,所以,我们手动的为隐形变量v,作一下虚函数表的初始化。

随后,我们就可以写一个函数test1,用来作struct B的虚函数调用,为了和class的虚函数作对比,我们也写一个函数test2,用来作class A的虚函数调用:

图片

如你所见,虚函数的调用部分,也就是函数test1和函数test2对应的CPU指令,是完全相同!注意哟,它们可都是货真价实的动态绑定。

最后,由于数组和指针常常可以混用的原因,我们还可以把调用struct B的“虚函数”代码,改写成数组的形式,这样,当有多个“虚函数”的时候,我们仅仅调整一下数组的索引,就可以实现不同“虚函数”的调用了:

typedef long (FUNC)(struct B b);
struct B{
FUNC* v;
long x;
long y;
};

long func1(struct B* b){ return b->x; }
long func2(struct B* b){ return b->y; }
long func3(struct B* b){ return b->x + b->y; }

FUNC vfuncTable[] = {
func1,
func2,
func3
};

struct B b = { .v = vfuncTable };
void test(struct B* p)
{
p->v0;//call func1
p->v1;//call func2
p->v2;//call func3
}
如你所见,仅仅通过简单的数组偏移,就可以迅速切换到不同的“虚函数”,并没有出现所谓的搜索、查询虚函数表的行为。这个时候,你还会担心调用虚函数,会大大增加系统开销吗?

至此,面向对象的几大特性:封装、继承、虚函数,我们都能用C语言中的struct,重新实现一遍。如果配合类型转换,我们甚至还可以实现多态,或许有效的重复利用所学的C语言知识,也能帮助我们快速理解C++的运作原理,您觉得呢?

06

总结

排除编译器对访问权限的限制,struct和class在数据结构上,特别是在内存布局上面,有很多相似之处。也正是这个原因,我们用C语言,依然可以实现诸如:继承、虚函数等面向对象的效果。

所以,我们也经常可以在Linux内核的文件系统、驱动框架中,看到很多类似的代码。当然,虽然面向对象的效果是达到了,但显然没有用C++的class那么简洁、优雅,再加上满天飞的函数指针,可读性也会差很多。

当然,这里我们只讨论C语言中的struct跟class之间的差异,因为C++中的struct,已经被强化的很厉害了,甚至可以定义构造函数和析构函数,已经非常接近class了。

07

热点问题

Q1:凭什么说不能在struct的内部定义成员函数?用函数指针就可以实现这样的效果呀,例如下面的代码:

typedef void (*PFUNC)(int a);

struct B{
PFUNC* memFunc;
long x;
long y;
};
A1:不错,虽然我们可以在struct内部定义一个函数指针,间接的实现内部成员函数,这从形式上看,确实跟class的成员函数有些相似之处。但要知道这里的函数指针变量memFunc是需要占用内存的:图片现在的结构体b不仅有两个8字节的long类型变量x和y,还有一个函数指针memFunc,所以它所占据的内存空间,从以前的16字节,扩大到了24字节。而且随着成员函数的增加,也将占用更多的存储空间。同时,初始化这些函数指针也是一个繁琐、必须的工作,这跟class实现成员函数的策略,是完全不同的。

Q2:如何用C语言的struct实现class中的private和protected访问属性呢?

A2:一般来说,C语言编译器是不支持这种private和protected访问属性的。对于如private和protected的访问属性的判定,是C++编译器在语法分析阶段中得出的,对于不合规的访问属性,在这个阶段就被禁止或报错;就像一行代码的末尾没有加分号一样,会被编译器无情的报错,不会继续生成CPU指令。

而一旦通过编译,由private和protected修饰的成员变量或成员函数,跟public修饰的成员变量或成员函数并无本质区别,CPU对它们都一视同仁的。

Q3:所以,面向对象只是一种编程思想,并不是一定要用C++的class来实现,实际上用C语言,也可以实现面向对象的编程思想,对吗?

A3:是的!这是非常经典的阐述。但如果没有本文的铺垫,这可能是一种很难理解的正确结论。但一旦了解底层实现,这种感觉可能就会脱口而出,少很多理解上的烦恼。由于编程语言和编程思想往往是你中由我,我中有你,所以,有时候通过对细节的追踪、挖掘,也能帮助我们准确理解一些抽象、奇怪的知识,更好的区分出语言和思想的边界。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园失物招领系统管理系统按照操作主体分为管理员和用户。管理员的功能包括字典管理、论坛管理、公告信息管理、失物招领管理、失物认领管理、寻物启示管理、寻物认领管理、用户管理、管理员管理。用户的功能等。该系统采用了Mysql数据库,Java语言,Spring Boot框架等技术进行编程实现。 校园失物招领系统管理系统可以提高校园失物招领系统信息管理问题的解决效率,优化校园失物招领系统信息处理流程,保证校园失物招领系统信息数据的安全,它是一个非常可靠,非常安全的应用程序。 ,管理员权限操作的功能包括管理公告,管理校园失物招领系统信息,包括失物招领管理,培训管理,寻物启事管理,薪资管理等,可以管理公告。 失物招领管理界面,管理员在失物招领管理界面中可以对界面中显示,可以对失物招领信息的失物招领状态进行查看,可以添加新的失物招领信息等。寻物启事管理界面,管理员在寻物启事管理界面中查看寻物启事种类信息,寻物启事描述信息,新增寻物启事信息等。公告管理界面,管理员在公告管理界面中新增公告,可以删除公告。公告类型管理界面,管理员在公告类型管理界面查看公告的工作状态,可以对公告的数据进行导出,可以添加新公告的信息,可以编辑公告信息,删除公告信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值