静态联编与动态联编

📅 2026/7/13 15:50:07 👁️ 阅读次数 📝 编程学习
静态联编与动态联编

静态联编与动态联编

一、联编的基本概念

联编是指计算机程序将标识符名存储地址关联起来的过程,也就是将函数调用与函数入口地址相结合的过程**。

联编的本质(函数调用与函数入口地址相结合) 函数调用: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 语言所有函数调用静态联编无继承和多态
对象名调用虚函数静态联编编译时类型确定
对象名调用普通函数静态联编非虚函数
指针调用虚函数动态联编运行时类型可能变化
引用调用虚函数动态联编运行时类型可能变化
指针调用普通函数静态联编非虚函数
构造函数中调用虚函数静态联编派生类未构造完成
析构函数中调用虚函数静态联编派生类已开始析构