前言
当代码写复杂后,一定会封装出大量的函数,这会导致两个问题:
①函数越多,栈的消耗也越厉害
疑问:为什么代码复杂了、函数变多了,栈消耗的就很厉害?
答:因为这会导致函数的调用深度可能会很深,比如:
fun1 --> fun2 --> fun3 --> fun4 --> fun5 ---> ...
在这些函数都没有返回之前,所有函数所消耗的栈空间,将一直不会被释放。如果复杂程序还有大量使用线程的话,线程函数还会占用栈空间。
②函数的调用过程,会多花费更多额外的时间
调用函数时,除了函数代码本身执行的时间外,还需要花费额外的时间,比如:
1)从调用位置跳转到函数代码处
2)从栈中开辟空间,将形参、自动局部变量、返回地址压栈
3)利用返回地址返回,以及返回时的弹栈
这些都是需要额外时间的,特别是如果这个函数的调用非常频繁的话,累积花费的就更多。
解决之道
对于那些被频繁调用,而且“代码很简短”的函数来说,我们往往就使用“带参宏”和“内联函数”代替,以减少这类函数的数量,如此一来:
1)函数减少了,在一定程度上节约了栈内存
2)消除了函数调用所需的额外时间,效率更高
C/C++都支持带参宏、内联函数
带参宏
参考
宏——基础
宏——高级
代码演示
1 #include <stdio.h> 2 3 int my_max(int a, int b) 4 { 5 a *= 2; 6 b /= 3; 7 return (a>b) ? a : b; //找出最大值 8 } 9 10 int main(void) 11 { 12 int ret = 0; 13 ret = my_max(25, 30); 14 printf("ret = %d\n", ret); 15 return 0; 16 }
如果my_max调用非常频繁的话,做成函数形式,其实是非常不划算的,所以完全可以使用带参宏来代替。
1 #include <stdio.h> 2 #define MY_MAX(a, b, ret) \ 3 ret = ((a*2)>(b/3)) ? (a*2) :(b/3) 4 5 6 int main(void) 7 { 8 int ret = 0; 9 MY_MAX(25, 30, ret); 10 printf("ret = %d\n", ret); 11 return 0; 12 }
进行宏替换后,main函数就变为了
1 int main(void) 2 { 3 int ret = 0; 4 5 ret = ((25*2)>(30/3)) ? (25*2) :(30/3); //找出最大值 6 printf("ret = %d\n", ret); 7 8 return 0; 9 }
使用宏来代替后,“宏体”就变成了“引用者”的一部分,如此一来不仅节约了栈空间,也免去了函数调用时的额外开销。
使用带参宏代替函数时的要求
①只用于对3~5行代码的简短函数进行替换,为什么只替换3~5行代码的简短函数呢?
使用宏来代替函数,缺点也是很明显的,因为宏替换后,使用宏的函数的代码会增加,如果很多函数都有使用这个宏的话,程序的代码量就会急剧增加,毕竟代码也是需要存储空间的。为了节省点栈空间、以及降低“函数调用”所消耗的额外时间,结果导致整个程序代码的剧增,这就得不偿失了。
②而且只替换被频繁调用的函数
替换这类函数才有明显的效果,否则占那么点小便宜也没有什么意思。
带参宏的缺点
带参宏的参数只用于替换,不涉及参数类型的检查,所以如果我们把参数写错了,在预编译进行宏替换时,是不会提示错误或者警告的,这不利于代码排错。
1 #define MY_MAX(a, b, ret) \ 2 ret = ((a*2)>(b/3)) ? (a*2) :(b/3); 3 4 int main(void) 5 { 6 int ret = 0; 7 MY_MAX(dsf344, 30, ret); 8 printf("ret = %d\n", ret); 9 return 0; 10 }
参数指定为dsf344肯定是错的,但是宏在替换时,只进行替换,不会进行参数检查,但是这个你要是搁在函数里面,函数会进行严格的参数检查,如果不对就会报错。为了改进带参宏不会进行参数检查的缺点,后来从c99标准开始就有了"内联函数"。
内联函数
内联函数是个啥
内联函数既不是函数也不是宏,它一个兼具宏和函数共同特点的这么个特殊的玩意。
①具有宏的特点,会像宏一样进行替换
不过宏是在“预处理阶段”进行替换的,而内联函数则是在“编译、链接”阶段进行替换的。替换的过程也被称为“内联”的过程,所以才被称为“内联函数”。
②也具有函数的特点,会像函数一样进行参数类型检查
一句话来概括的话,内联函数就是会像函数一样进行“参数类型检查”的带参宏。
内联函数举例
inline关键字
这个是内联函数所使用的关键字,标记了这个关键字的函数就是内联函数,不过只有标记“函数定义”时才是有效的,至于声明,标不标记inline都无所谓。由于内联函数和“宏”有点类似,而宏经常放在.h中,所以我们一般习惯于将“内联函数”的定义放在.h中,不过如果将函数放在.h中,而且函数如果是extern的话,会报重复定义的错误,所以我们往往需要将其修饰为static。