c++ 动态绑定

204 阅读3分钟

1. 概念

动态绑定又名动态联编;我们分开解释

1.1 绑定

绑定的定义出现在编译器中,主要是指源码中的变量名或者函数替换为内存地址;

1.2 动态

有动态肯定有静态

静态发生在编译期,即编译为可执行文件的阶段

动态发生在运行期,即可执行文件运行时的阶段

2. 原因

2.1 为什么需要绑定

   1. 源代码写的都是类,变量,方法
   
   2. cpu 执行的是指令,指令的执行需要数据的内存地址;
       如 cpu 执行一个int c = a + b;需要先加载 a b的值,这个加载指令需要 a b 的内存地址,
       其实这个时候 CPU 压根儿不知道 a b, 它只知道 ab 的内存地址,
       比如 a 的内存地址为0x1,b 的内存地址为 0x2,上述表达式可以翻译为(0x1)+ (0x2)
       看到没,CPU 压根儿不知道 a b 这两个符号
       
   3. 编译和连接器需要把源码符号 a b 转为 CPU 可识别的地址(0x1 0x2)
   
   4. 这个过程就是绑定过程
   

2.2 为什么需要动态绑定

废话,因为有需求呗,比如?????

c++是一个面向对象的语言,这里面有个基本需求就是基类指针指向子类对象,这样通过继承和多态提高代码的可扩展性。再具体一点,来个例子:

class Animal{
    virtual void eat(){}
}

class Cat: pulic Animal{
    virtual void eat(){
        cout << "cat eat";
    }
}

class Dog: pulic Animal{
    virtual void eat(){
        cout << "dog eat";
    }
}

Animal *p;
int n;
cin >> n;
if(n > 10){
    p = new Cat("cat", 16);
}else{
    p = new Dog("dog", 22);
}
p -> eat();

上面代码在编译的时候,需要解决 P 指向谁的问题;

发现 p 的类型只有在运行时才可以知道,进而也没有办法确定调用哪个eat

3. 原理

当然上面只是列举了动态绑定的一个使用场景,我们来看看它的具体实现

对于上面实例,编译和链接阶段如何做的才达到这个效果呢

首先对于某个类,如果它的成员方法有 virtual 或者它的父类有 virtual 方法,就会生成一个虚函数表
该虚函数表里存放的是个每个虚函数的内存地址

编译器在编译时会给每个类增加一个隐藏属性,使该属性指向这个虚函数表

当对象类型确定后,比如上例中 P,假设为 Dog 类型

那么通过对象 P 的隐藏属性就可以找到它的虚函数表

从它的虚函数表中即可查到 它的 eat 内存地址,进而发生调用

其实我们可以发现,对于 Dog 类型增加的隐藏属性,它是在编译的时候就加入了,而且是在编译期时就指向了这个虚函数表

只是因为我们在编译的时候不知道 P 到底是 Cat 类型还是 Dog 类型,进而我们无法绑定 p -> eat()的内存地址

现在编译器中,其实对于虚函数无论在编译期能不能确定它的类型,都是采用这种方式进行的,估计是为了统一吧

有个问题,就是自动增加的这个隐藏属性可不可以为静态的????

答:不能是静态的,假设存在继承关系,那么这个成员只能指向某个虚函数表,而如果子类也有其他虚函数,导致基类和子类的虚函数表是不同的,进而需要指向不同的虚函数表

4. 问题

运行时是如何确定 P 的类型的呢,我也不知道,留着以后学习吧