多态性在C++和Linux内核中的实现共性和区别

Linux内核是用C语言开发的,而C是面向结构,面向过程的语言,这种语言的特点是数据结构和数据操作是分离的两个部分,但是这并不代表面向过程的语言无法实现面向对象语言的一些机制,比如多态性,多态的本质在于其抽象的思维方法,而并非是其实现技巧。换句话说,用C语言也同样可以实现类似多态的机制。

Linux内核中普遍使用了这种思想,比如container_of可以通过一个基类对象得到它的子类对象。而设备驱动最常用的操作模型,open/read/write/ioctl/close则是最能够体现用C语言进行多态实现的最好案例。

linux内核没有编译器的协助,无法静态的为每个多态类生成虚拟操作表对象,所以必须要唤醒自身额外的技能来完成这个操作,该操作不能在编译期完成,必须要在运行期实现,在字符设备驱动中,实现的关键一步是def_chr_fops的定义。它的超然地位源于,def_chr_fops是将所有设备的必要操作进行了更高一层的抽象--->每个设备都需要打开操作。所以,在每个驱动的必经之路上,Linux内核插入了chrdev_open函数,在此函数中,完成了设备号到具体设备的转换和映射,并且将原来的def_chr_fops替换为通过设备号得到的驱动真正的fops。之后,所有的read/write/ioct/close将会直接调用到驱动真正的FOPS中的实现。

Linux内核中多态的延迟绑定

多态性在C++和Linux内核中的实现共性和区别_第1张图片

以上分析了Linux内核的多态的实现机制,现在我们再分析一下CPP中静态的多态是如何实现的:

CPP的虚函数表延迟绑定

先看一段代码:

#include 
using namespace std;

class Base
{
public:
    virtual void fn() { cout << "In Base class" << endl; }
};
class Sub :public Base
{
public:
    virtual void fn() { cout << "In Sub class" << endl; }
};

int main(int argc, char **argv)
{
    Base *p;

    Base bc;
    Sub sc;
	
    p=&bc;
    p->fn();
    p=≻
    p->fn();

    return 0;
}

编译:

调试:

多态性在C++和Linux内核中的实现共性和区别_第2张图片

通过调试我们可以看到,父子两个对象的虚函数表地址都已经打印出来了。但是两个函数表的地址究竟是静态分配的还是运行时动态给的呢?我们还不得而知,不过,如果反编译ELF文件,能够得到这两个地址,那说明虚函数表一定是静态分配,静态绑定的无疑了。看一下是不是这样:

objdump -D a.out >a.dis

从反编译文件中查找地址0x7afc50和0x7afc68,我们最后还是在ELF中找到了这两个对象的势力范围,可以看到它们确实是静态分配的。

多态性在C++和Linux内核中的实现共性和区别_第3张图片

并且,用c++filt对_ZTV4Base和_ZTV3Sub进行反命名处理,我们得到了他们属于virtual function table的最直接的证据。

结论:所以,不同于Linux内核在运行时进行的延迟绑定,CPP中虚函数的实现,是编译器负责分配静态存储并填充对象的虚函数表,从而实现静态的绑定。

总结:

其实,在Inside The C++ Object Model(中文版名称是深度探索C++对象模型)的4.2节,已经明确给出:“在C++中,virtual funcdtions(可经由其class object被调用)可以在编译时期获得,此外,这一组地址是固定不变的,执行期不可能新增或替换之,由于程序执行时,表格的大小和内容不会改变,所以其建构和村去皆可以由编译器完全掌握,不需要执行期的任何介入” 

拜编译器所赐,CPP实现多态机制几乎是透明的,程序员几乎不用做什么。但是在Linux内核中,却有另外一套优美的机制来达到同样的目的,大家殊途同归,实现了一模一样的功能。山重水复疑无路,柳暗花明又一村,有些时候,没有绝对的“是”与“否”,我们思维中固定的观念,通常也是可以调整甚至是替换的,没有绝对,没有绝对,没有绝对。 


结束

你可能感兴趣的:(数学,嵌入式系统,c++,linux,开发语言)