普通函数
- 调用时向栈中push函数帧,调用结束后pop函数帧。编译器会在函数调用语句的前后,插入入栈和出栈的辅助代码。
- 函数帧中存在的东西有:实参(值传递,如果参数是对象则调用拷贝构造函数)、返回地址(指向caller的下一句指令)、原栈指针%ebp、局部变量、某些通用寄存器的快照等。
- 存在入栈出栈、复制参数、快照通用寄存器的开销,调用代价大于内联函数。
内联函数
比普通函数调用性能高,体现在
- 没有函数调用的开销,主要是参数压栈、栈帧开辟与回收,以及寄存器保存与恢复
- 编译器能掌握更多代码的信息,有助于编译器更深入地优化代码。编译器在处理调用内联函数的函数时,因为可供分析的代码更多,因此它能做的优化更深入彻底。前一条优点对于开发人员来说往往更显而易见一些,但往往这条优点对最终代码的优化可能贡献更大。
注意点
- 由编译器完成展开。
- 由于CPP是按编译单元编译的。一个编译单元被编译时不知道其它编译单元的存在。所以假设此编译单元中需要展开某个内联函数,那么这个内联函数必须在此编译单元内可见。又根据“唯一定义原则”,内联函数的最佳实践是将内联函数的定义放入头文件中;所有需要用到这个内联函数的编译单元都include该头文件。(显然根据缺点2,若该头文件中内联函数的实现变了,那么所有include该头文件且调用了该内联函数的编译单元都得重新编译)
内联函数的缺点
- 缺点1:当内联函数函数体内的代码量大于普通函数入栈、出栈的辅助代码,且编译单元中大量调用此内联函数时,大量的函数展开将增大代码体积。
- 缺点2:大型项目中,多个编译单元使用到同一内联函数时,修改此内联函数的实现将导致这些编译单元都得重新编译。如果是普通函数调用则仅有函数的实现文件需要重新编译。
宏
- 由预处理器完成展开。
- 不是函数,不属于编译器管理范畴,没有类型检查机制。
- 是文本替换,在想当然的在参数上使用++、--等操作符时,可能出现奇怪的问题。