浅析静态绑定和动态绑定
来源:互联网 发布:硬妹风的淘宝店 编辑:程序博客网 时间:2024/05/29 16:27
静态绑定 & 动态绑定
- 静态绑定(statically bound),又名前期绑定(early binding);
- 动态绑定(dynamically bound),又名延期绑定(late binding)。
ps 英文名称摘自《Effective C++》 条款37。此条款中有关于“静态类型、动态类型”的描述。
在 C 语言中并没有“静态绑定”、“动态绑定”的概念(至少我没有查到)。
我理解的 C++ 类的内存模型其实就是 C 语言中的 struct 结构体。但成员函数又是归属于类所有的,所以就存在函数到类的绑定。
- 静态绑定指的就是这种绑定关系(映射关系)是在编译期间确定的;
- 动态绑定指的就是这种绑定关系(映射关系)在编译期间确定不了,得等到程序运行、执行期间才能最终确定
我们朴素地分析一下静态绑定、动态绑定。函数都有其地址,函数调用翻译成汇编代码其实就是直接用地址,很明显在汇编代码这个层次(甚至不用这么底层,C语言层次就行)不同的函数实现有不同的地址(使用C 语言的话,就有不同的函数名),但在 C++、Java 高级语言这个层次,不同的实现可能有相同的函数名(有很多种情况:重载、不同类里面相同名称、模板、继承体系中的重写),在 C++ 代码中出现一个函数调用(函数名),怎么正确地找到对应的实现(地址)呢?
- 重载:因为参数类型或者个数不同其实是有区分的,直接在高级语言下一个层次(比如C语言层面)使用不同的命名重新包装就可以了。调用时根据传参的情况,再映射就可以了。
- 不同的 class 里面相同名称:编译器实现这个完全可以和重载情形使用同样的方案。维护一个映射表就可以。
- 模板:暂时不了解
- 继承体系的重写:
我们都知道 override 函数时,两个函数的声明式肯定是一模一样的,如果不考虑 override 的概念(具体到代码中就是不使用 virtual
关键字),那么其场景和上述第 2 中就是一样的——如果是 base 类型(即便是指针)就调用 base class 的函数,如果是 derived 类型就调用 derived class 的函数。事实上都是编译期间根据维护的映射表“偷梁换柱”(映射是在声明的类型(函数声明式、类类型)-具体的函数实现之间),直接把对应的地址拿过来,ok,汇编代码完成了。
继承体系中允许 base 指针是可以指向 derived 对象,但编译器依旧是根据声明指针时的类型去映射具体的函数实现的,所以会出现一些变态的现象:
- class Base 无 void func(),class Derived 有 void func(),我们执行
Base *p=new Derived(); p->func();
会报错找不到 - class Base 的 void func() 打印 base,class Derived 的 void func() 打印 derived,我们执行
Base *p=new Derived(); p->func();
会打印出 base 纳尼 - class myclass 有函数 simple(),函数实现中没有对 this 解引用的操作(不管是显式的还是隐在的),我们执行
myclass *p=NULL; p->simple();
能够正确执行
so,正如我们看到的,这就是静态绑定。
随着 OO 越来与流行,为了获得多态性,我们想要打破这种规则——继承体系中允许基类指针指向派生类对象,在此基础上我们想让基类指针可以调用派生类的函数,我们要让例二 p->func()
打印 derive 怎么办?
好吧,增添新的语言特性,使用关键词 virtual
,用来表明碰到这个类的指针(或引用)调用此函数时不要根据静态类型(声明指针的类型)映射具体实现,你们要根据这个指针指向的对象的实际类型来映射具体实现(即动态绑定)。编译器说,纳尼,我靠,我哪知道啊?我只解析 declaration,只分析了变量声明的类型,内存的初始化、赋值在运行期才发生呢……好吧,编译器感觉为难做不了这个事情,只能把这个找到(绑定)函数具体实现的步骤放到运行期间了,可是效率会低一些呢。如果你要多态性,只能接受了。
具体实现中通过 virtual
关键词标记延迟绑定,然后在运行过程中,根据对象内存中的虚函数表指针获得函数地址。
如果不是通过指针或引用调用虚函数,也是在编译期间就绑定的;而没有 virtual
修饰的函数根据调用者的静态类型在编译期间直接绑定。
// Base 类有虚函数 func()// Derived 继承自 Base,且 override 了 func() Base *p=new Derived(); p->func();
我们觉得一目了然的事情,比如编译器汇编时直接把
p->func()
调用换成 derived::func(p)
不就好了吗,实现起来很难吗?事实上编译器是卡在它只知道 p 是 base *
类型,它并不知晓 p 被初始化(赋值)了什么,它只生成“得到一个地址,把这个地址赋给 base 指针 p;根据 p 指明的地址调用 func()” 的指令,至于前一条“分配内存,初始化 derived 对象”的指令,现在是前后相邻紧挨着,其他场景可能这两条指令相差十万八千里呢。事实上我们只会在测试时写 Base *p=new Derived(); p->func();
这样的例子,在真实的业务场景中为了效率至少应该写成 Derived derived; derived.func();
,在能够确定类型的时候使用静态绑定效率更高。实际上真实的业务场景多是- 浅析静态绑定和动态绑定
- 动态绑定与静态绑定浅析
- 静态绑定和动态绑定
- 动态绑定和静态绑定
- 静态绑定和动态绑定
- 静态绑定和动态绑定
- 静态绑定和动态绑定
- 动态绑定和静态绑定
- 静态绑定和动态绑定
- 动态绑定和静态绑定
- 静态绑定 动态绑定
- C++中动态绑定和静态绑定
- C++中动态绑定和静态绑定
- c++的动态绑定和静态绑定
- C++ 静态绑定和动态绑定
- C++的动态绑定和静态绑定
- MyC++之动态绑定和静态绑定
- C++的动态绑定和静态绑定
- Redis主从复制及哨兵
- linux 文件系统原理
- 我的AI转型之路与AI之我见(非985211的奋斗路程与视角)
- java 计算方法执行时间
- MYSQL数据库安装与配置详解
- 浅析静态绑定和动态绑定
- Hbase Region的拆分和合并
- 服务器更新图片问题
- Git忽略已track的文件
- 了解 Spring Data JPA
- SAP GUI for Windows对象列表不能拖动的问题
- 伸缩布局flex
- 解析XML格式数据
- C#链接SQLServer实现插入和查询数据源代码