1,普通函数调用流程(栈的使用)
假设有一个普通函数:
void Print() { int x = 10; std::cout << x; } int main() { Print(); }调用过程(CPU 和栈视角):
- 调用
Print()
- CPU 执行
call Print- 当前栈帧(main 的栈)保存返回地址
- CPU 跳到
- 创建
- 栈顶分配局部变量空间(
int x = 10)- 保存必要的寄存器、返回地址等
- 函数体执行
- 打印 x
- 函数返回
- 弹出栈帧,恢复寄存器
- 用栈中保存的返回地址跳回 main
✅ 栈的特点:每个函数调用都会分配自己的栈帧
- 函数逻辑(代码)→ 固定在代码段
- 调用时的栈帧→ 为函数的局部变量、返回地址、寄存器开辟临时空间
- 函数执行逻辑在栈上运行,结束后释放空间
- 内联函数→ 减少栈帧创建,局部变量在调用函数栈帧
- 宏定义→ 文本替换,直接在调用点执行,也在调用函数栈帧
2, 内联函数的调用流程
假设:
inline void Print() { int x = 10; std::cout << x; } int main() { Print(); }调用过程:
- 调用点直接展开函数体(编译器把函数内容复制到调用点):
int main() {
int x = 10;
std::cout << x;
}
- 栈的变化
- 没有新的栈帧分配
- 变量
x直接在 main 的栈帧上分配- 不用压返回地址,不用跳转
- CPU 执行顺序
- 就像你在 main 里直接写了
int x = 10; std::cout << x;
✅ 栈的特点:
- 没有独立栈帧
- 局部变量在调用函数的栈帧里
- 减少了压栈、跳转、返回的开销
3,宏定义(#define)和内联函数的表面相似
#define SQUARE(x) ((x)*(x))
- 使用时:
int a = 5;
int b = SQUARE(a); // 编译器预处理时替换成 ((a)*(a))✅ 表面相似点:
- 都在调用处展开,没有函数调用开销
- 适合小功能,频繁调用也不会压栈
4,宏定义的本质(预处理阶段)
- 宏在编译前就被展开(预处理器阶段)
- 编译器根本没有“看到函数”,它只是文本替换
b = SQUARE(a); // 预处理后
b = ((a)*(a));
- 内存上根本没有函数体
- 不会有函数调用,也没有
inline之类的优化- 问题:没有类型检查、可能有副作用
总结对比
| 特性 | 普通函数 | 内联函数 |
|---|---|---|
| 栈帧 | 独立栈帧 | 不分配独立栈帧,局部变量在调用点栈帧里 |
| 返回地址 | 压栈 + ret | 无需压栈 |
| CPU 跳转 | call → 执行 → ret | 直接顺序执行展开代码 |
| 局部变量 | 在函数栈帧 | 在调用函数栈帧 |
这是图片对比,外加宏。直接全部搞懂