前言
函数调用要开辟栈帧,如果是一些稍微复杂的递归问题或者排序问题(含有交换比较多,例如快排)就会导致开辟的函数栈帧的数量太多了,那么有没有什么办法可以优化一下这个函数栈帧呢?
一、宏
对于前言中的问题,C语言给出的办法是——宏。
宏定义的函数,在预处理阶段就会将函数与程序中对应的语句进行替换,进而优化了多次调用函数所开辟的函数栈帧。既然C语言中有优化这个问题的方法,那么我们的C++为什么还要创造一种新方法呢?
我们先来回顾一下宏的优缺点:
1.宏的优缺点
(1)优点
①增强代码的复用性。
②提高性能。
(2)缺点
①不方便调试宏。(因为宏是在预编译阶段进行替换,无法调试)
②没有类型安全的检查。
③宏会导致代码可读性差,可维护性差,容易误用(易出错)。
2.C++中替代宏的方法
由于宏有这三个缺点,C++中给出了替代宏的方法:
(1)常量定义换用const enum
(2)短小函数定义换用内联函数
其中的const enum是C语言中就有的,内联函数却是C++新给出的概念。那么,接下来我们就来了解一下内联函数是什么,以及它为什么可以替换宏。
二、内联函数
使用inline关键字修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,因此没有函数调用建立栈帧的开销,进而提升程序运行的效率。
1.内联函数与宏函数的联系和区别
(1)联系
内联函数的作用和宏是一样的,都是将函数直接替换进程序,进而避免了函数栈帧。
(2)区别
因为内联函数的替换过程是在程序运行起来以后,所以可以进行调试,方便观察;
因为内联函数是直接在程序中展开,和其他函数是一样的,所以内联函数的参数类型是受限制的。
由此可见内联函数是优化了宏的缺点,同时具有宏的功能。
2.内联函数的特性
(1)内联函数是一种以空间换时间的做法
用函数体替换函数调用
(2) inline(内联函数)对编译器而言只是个建议,但是编译器不一定会采纳这个建议。
inline是建议编译器将 inline修饰的函数展开,但根据不同情况编译器具体决定是进行展开还是函数调用。
①如果内联函数是一个短函数(代码量较短),则编译器会将它展开,正常使用;
②如果内联函数是一个长函数(代码量较长),则编译器不会将它展开,而是用函数调用的方式使用这个函数。(如果内联函数是一个递归函数,也可能不会被展开)
为什么长函数不展开?
答:为了避免代码膨胀(代码膨胀是指代码有着不必要的长度、缓慢或者其他浪费资源的情况),会影响可执行程序的大小,进而影响安装包的大小(例子:更新软件需要安装包,如果出现代码膨胀,安装包的大小就会变大)
如何观察编译器是否对内联函数进行进行展开?
答:
1.在release模式下,查看编译器生成的汇编代码中是否存在call Add
2.在debug模式下,需要对编译器进行设置,否则不会展开。设置以后进行调试,转到汇编代码,就可以观察该内联函数在程序运行过程中是否被展开。
debug模式下,编译器默认不会对代码进行优化(方便调试),以下给出vs2013环境下的设置方式。
举个例子:
普通函数
int ADD(int x, int y)//普通函数 { return x + y; } int main() { int ret = 0; ret = ADD(10, 20); return 0; }
汇编代码:
内联函数
inline int ADD(int x, int y)//内联函数 { return x + y; } int main() { int ret = 0; ret = ADD(10, 20); return 0; }
汇编代码:
内联函数是否可以是递归函数?
一般来说,内联函数的机制用于优化规模小、流程直接、频繁调用的函数,很多编译器不支持内联递归函数,而且一个代码量太大的函数也不大可能在调用点内联地展开。
(3)内联函数不建议声明和定义分离
内联函数的声明和定义分离会导致链接错误,所以使用内联函数就直接在该源文件中定义即可。
为什么内联函数不能声明和定义分离?
答:内联函数在编译期间是不会生成地址的(编译器认为内联函数不需要函数地址,因为内联函数的使用方法,就是直接在程序内部展开,而不会进行函数调用。inline的修饰会让编译器觉得这个函数他就是个内联函数,所以无论它的代码长与短,在编译期间,编译器都不会给他生成函数地址。),因此,在编译期间源文件所生成的符号表中找不到内联函数,链接时就无法在符号表中找到对应的函数,就会导致链接错误。
例子:
当内联函数的声明和定义分离时
// test.h文件 #include <iostream> using namespace std; inline void f(int i); // test.cpp文件 #include "test.h" void f(int i) { cout << i << endl; } // main.cpp文件 #include "test.h" int main() { f(10); return 0; }
3.内联函数的优势和缺陷
(1)优势
减少了调用开销,提高了效率。
(2)缺陷
可能使目标文件变大(因为是将函数直接进行展开,所以会增加代码量)
总结
以上就是今天要讲的内容,本文介绍了宏和内联函数的相关概念,主要介绍了内联函数的特性。本文作者目前也是正在学习C++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!