静态联编与动态联编
静态联编与动态联编
一、联编的基本概念
联编是指计算机程序将标识符名与存储地址关联起来的过程,也就是将函数调用与函数入口地址相结合的过程**。
联编的本质(函数调用与函数入口地址相结合) 函数调用:func(10, 20) 联编前: call func (未知地址) ↓ 联编过程 联编后: call 0x00401234 (具体内存地址)二、联编的分类
| 类型 | 别名 | 绑定时机 | 实现方式 | 效率 |
|---|---|---|---|---|
| 静态联编 | 早期绑定 | 编译/链接阶段 | 直接函数调用 | 高 |
| 动态联编 | 晚期绑定、滞后联编 | 运行阶段 | 通过虚表间接调用 | 略低 |
三、静态联编(早期绑定)
3.1 静态联编的定义
静态联编是指在编译和链接阶段,就将函数实现和函数调用关联起来。
C语言中的所有联编都是静态联编,C++中也支持静态联编。
静态联编的处理流程 源代码 编译阶段 func(10, 20); --> 编译器查找 func 的函数地址 ↓ 生成直接调用指令 call 0x00401234 特点: 地址在编译时就已确定 执行时直接跳转到该地址 没有任何运行时开销3.2 触发静态联编的情况
3.2.1 通过对象调用虚函数(使用.运算符)
classObject{public:virtualvoidadd(intx){cout<<"Object::add(int)"<<endl;}};intmain(){Object obj;obj.add(10);// ! 通过对象调用虚函数 → 静态联编return0;}原因:通过对象名调用时,对象的类型在编译时完全确定,编译器可以直接确定调用哪个函数。
3.2.2 普通函数调用
voidfunc(intx){cout<<x<<endl;}intmain(){func(10);// 静态联编:地址在编译时确定return0;}3.2.3 非虚成员函数调用
classObject{public:voidfunc(){cout<<"Object::func"<<endl;}// 非虚函数};intmain(){Object obj;obj.func();// 静态联编return0;}四、动态联编(晚期绑定)
4.1 动态联编的定义
动态联编是指在程序执行时才将函数实现和函数调用关联起来。
C++中通过基类指针或引用调用虚函数时,会在运行时根据实际对象类型选择调用哪个函数。
动态联编的处理流程 源代码 编译阶段 p->add(10); ----> 生成间接调用代码 mov eax, [p] mov ecx, [eax] call [ecx + offset] ↓ 运行阶段:根据 p 实际指向的对象类型动态决定 p 指向 Object → 调用 Object::add() p 指向 Base → 调用 Base::add()4.2 触发动态联编的条件
| 条件 | 说明 |
|---|---|
| 1. 公有继承 | 派生类 is-a 基类 |
| 2. 虚函数 | 基类函数声明为 virtual |
| 3. 派生类重写 | 派生类提供自己的实现 |
| 4. 基类指针/引用 | 通过指针(->)或引用(.)调用 |
classObject{public:virtualvoidadd(intx){cout<<"Object::add(int)"<<endl;}};classBase:publicObject{public:virtualvoidadd(intx)override{cout<<"Base::add(int)"<<endl;}};intmain(){Base base;Object*p=&base;p->add(10);// 动态联编:调用 Base::add()Object&r=base;r.add(10);// 动态联编:调用 Base::add()return0;}五、构造与析构中的联编
5.1 构造函数中的联编
在构造函数中调用虚函数时,采用静态联编(不会调用派生类的重写版本)。
classObject{public:Object(intx=0):value(x){cout<<"Create Object: "<<endl;add(12);// !调用 Object::add(),不是派生类的版本}virtualvoidadd(intx){cout<<"Object::add(int)"<<endl;}private:intvalue;};classBase:publicObject{public:Base(intx=0):Object(x+10),num(x){cout<<"Create Base"<<endl;add(1);// 此时虚表已指向 Base,调用 Base::add()}virtualvoidadd(inta)override{cout<<"Base::add(int)"<<endl;}private:intnum;};intmain(){Base base;return0;}输出:
Create Object: Object::add(int) ← 构造函数中调用的是 Object 版本 Create Base Base::add(int) ← 此时虚表已设置,调用 Base 版本原因图解:
构造过程中的虚表指针变化 Base base; 构造过程: 阶段1:Object 构造函数执行 base._vfptr → Object 虚表 此时 add() 调用 Object::add() ↓ 阶段2:Base 构造函数执行 base._vfptr → Base 虚表 此时 add() 调用 Base::add() 结论:构造过程中,虚表指针逐级更新 当前构造到哪一层,虚表就指向哪一层5.2 析构函数中的联编
在析构函数中调用虚函数时,也采用静态联编。
classObject{public:virtual~Object(){cout<<"Destroy Object"<<endl;add(23);// !调用 Object::add(),不是派生类的版本}virtualvoidadd(intx){cout<<"Object::add(int)"<<endl;}};classBase:publicObject{public:~Base()override{cout<<"Destroy Base"<<endl;add(2);// 此时调用 Base::add()}voidadd(inta)override{cout<<"Base::add(int)"<<endl;}};intmain(){Base base;return0;}输出:
Destroy Base Base::add(int) ← 先调用派生类析构,此时虚表仍指向 Base Destroy Object Object::add(int) ← 基类析构时,虚表已恢复为 Object析构过程中的虚表指针变化:
析构过程中的虚表指针变化 Base base; 析构过程: 阶段1:Base 析构函数执行 base._vfptr → Base 虚表 此时 add() 调用 Base::add() ↓ 阶段2:Object 析构函数执行 ↓ base._vfptr → Object 虚表(恢复) 此时 add() 调用 Object::add()六、静态联编 与 动态联编对比
6.1 核心区别对比表
| 对比维度 | 静态联编 | 动态联编 |
|---|---|---|
| 绑定时机 | 编译/链接阶段 | 运行阶段 |
| 函数地址确定 | 编译时确定 | 运行时确定 |
| 调用指令 | 直接调用call address | 间接调用call [vptr+offset] |
| 性能开销 | 无 | 一次间接寻址 |
| 灵活性 | 低 | 高 |
| 适用场景 | 类型确定的情况 | 需要多态的情况 |
6.2 调用方式对比
| 调用方式 | 联编类型 | 示例 |
|---|---|---|
对象 +.调用虚函数 | 静态联编 | obj.add(10) |
对象 +.调用普通函数 | 静态联编 | obj.func() |
指针 +->调用虚函数 | 动态联编 | p->add(10) |
引用 +.调用虚函数 | 动态联编 | r.add(10) |
指针 +->调用普通函数 | 静态联编 | p->func() |
七、完整示例代码
#include<iostream>usingnamespacestd;classObject{private:intvalue;public:Object(intx=0):value(x){cout<<"Object 构造"<<endl;add(12);// 静态联编:调用 Object::add()}virtual~Object(){cout<<"Object 析构"<<endl;add(23);// 静态联编:调用 Object::add()}virtualvoidadd(intx){cout<<"Object::add("<<x<<")"<<endl;}voidnormalFunc(){cout<<"Object::normalFunc"<<endl;}};classBase:publicObject{private:intnum;public:Base(intx=0):Object(x+10),num(x){cout<<"Base 构造"<<endl;add(1);// 此时虚表已指向 Base,调用 Base::add()}~Base()override{cout<<"Base 析构"<<endl;add(2);// 调用 Base::add()}voidadd(inta)override{cout<<"Base::add("<<a<<")"<<endl;}voidnormalFunc(){cout<<"Base::normalFunc"<<endl;}};intmain(){cout<<"=== 对象调用(静态联编) ==="<<endl;Base base;base.add(100);// 静态联编:通过对象调用base.normalFunc();// 静态联编:普通函数cout<<"\n=== 指针调用(动态联编) ==="<<endl;Object*p=&base;p->add(200);// 动态联编:通过指针调用虚函数p->normalFunc();// 静态联编:普通函数cout<<"\n=== 引用调用(动态联编) ==="<<endl;Object&r=base;r.add(300);// 动态联编:通过引用调用虚函数r.normalFunc();// 静态联编:普通函数return0;}输出:
=== 对象调用(静态联编) === Object 构造 Object::add(12) ← 构造时静态联编 Base 构造 Base::add(1) ← 构造时动态联编(虚表已设置) Base::add(100) ← 通过对象调用(静态) Base::normalFunc === 指针调用(动态联编) === Base::add(200) ← 通过指针调用虚函数(动态) Object::normalFunc ← 普通函数(静态) === 引用调用(动态联编) === Base::add(300) ← 通过引用调用虚函数(动态) Object::normalFunc ← 普通函数(静态) Base 析构 Base::add(2) ← 析构时调用 Object 析构 Object::add(23) ← 析构时静态联编八、总结
| 场景 | 联编类型 | 原因 |
|---|---|---|
| C 语言所有函数调用 | 静态联编 | 无继承和多态 |
| 对象名调用虚函数 | 静态联编 | 编译时类型确定 |
| 对象名调用普通函数 | 静态联编 | 非虚函数 |
| 指针调用虚函数 | 动态联编 | 运行时类型可能变化 |
| 引用调用虚函数 | 动态联编 | 运行时类型可能变化 |
| 指针调用普通函数 | 静态联编 | 非虚函数 |
| 构造函数中调用虚函数 | 静态联编 | 派生类未构造完成 |
| 析构函数中调用虚函数 | 静态联编 | 派生类已开始析构 |
