前言

当代码写复杂后,一定会封装出大量的函数,这会导致两个问题:

①函数越多,栈的消耗也越厉害

疑问:为什么代码复杂了、函数变多了,栈消耗的就很厉害?

:因为这会导致函数的调用深度可能会很深,比如:

fun1 --> fun2 --> fun3 --> fun4 --> fun5 ---> ...

在这些函数都没有返回之前,所有函数所消耗的栈空间,将一直不会被释放。如果复杂程序还有大量使用线程的话,线程函数还会占用栈空间。

②函数的调用过程,会多花费更多额外的时间

调用函数时,除了函数代码本身执行的时间外,还需要花费额外的时间,比如:

1)从调用位置跳转到函数代码处
2)从栈中开辟空间,将形参、自动局部变量、返回地址压栈
3)利用返回地址返回,以及返回时的弹栈

这些都是需要额外时间的,特别是如果这个函数的调用非常频繁的话,累积花费的就更多。

解决之道

对于那些被频繁调用,而且“代码很简短”的函数来说,我们往往就使用“带参宏”和“内联函数”代替,以减少这类函数的数量,如此一来:

1)函数减少了,在一定程度上节约了栈内存

2)消除了函数调用所需的额外时间,效率更高

C/C++都支持带参宏、内联函数

带参宏

参考

宏——基础

宏——高级

代码演示

 #include <stdio.h>

 int my_max(int a, int b)
{
a *= ;
b /= ;
return (a>b) ? a : b; //找出最大值
} int main(void)
{
int ret = ;
ret = my_max(, );
printf("ret = %d\n", ret);
return ;
}

如果my_max调用非常频繁的话,做成函数形式,其实是非常不划算的,所以完全可以使用带参宏来代替。

 #include <stdio.h>
#define MY_MAX(a, b, ret) \
ret = ((a*)>(b/)) ? (a*) :(b/) int main(void)
{
int ret = ;
MY_MAX(, , ret);
printf("ret = %d\n", ret);
return ;
}

进行宏替换后,main函数就变为了

 int main(void)
{
int ret = ; ret = ((*)>(/)) ? (*) :(/); //找出最大值
printf("ret = %d\n", ret); return ;
}

使用宏来代替后,“宏体”就变成了“引用者”的一部分,如此一来不仅节约了栈空间,也免去了函数调用时的额外开销。

使用带参宏代替函数时的要求

①只用于对3~5行代码的简短函数进行替换,为什么只替换3~5行代码的简短函数呢?

使用宏来代替函数,缺点也是很明显的,因为宏替换后,使用宏的函数的代码会增加,如果很多函数都有使用这个宏的话,程序的代码量就会急剧增加,毕竟代码也是需要存储空间的。为了节省点栈空间、以及降低“函数调用”所消耗的额外时间,结果导致整个程序代码的剧增,这就得不偿失了。

②而且只替换被频繁调用的函数

替换这类函数才有明显的效果,否则占那么点小便宜也没有什么意思。

带参宏的缺点

带参宏的参数只用于替换,不涉及参数类型的检查,所以如果我们把参数写错了,在预编译进行宏替换时,是不会提示错误或者警告的,这不利于代码排错。

 #define MY_MAX(a, b, ret)  \
ret = ((a*)>(b/)) ? (a*) :(b/); int main(void)
{
int ret = ;
MY_MAX(dsf344, , ret);
printf("ret = %d\n", ret);
return ;
}

参数指定为dsf344肯定是错的,但是宏在替换时,只进行替换,不会进行参数检查,但是这个你要是搁在函数里面,函数会进行严格的参数检查,如果不对就会报错。为了改进带参宏不会进行参数检查的缺点,后来从c99标准开始就有了"内联函数"。

内联函数

内联函数是个啥

内联函数既不是函数也不是宏,它一个兼具宏和函数共同特点的这么个特殊的玩意。

①具有宏的特点,会像宏一样进行替换

不过宏是在“预处理阶段”进行替换的,而内联函数则是在“编译、链接”阶段进行替换的。替换的过程也被称为“内联”的过程,所以才被称为“内联函数”。

②也具有函数的特点,会像函数一样进行参数类型检查

一句话来概括的话,内联函数就是会像函数一样进行“参数类型检查”的带参宏。

内联函数举例

inline关键字

这个是内联函数所使用的关键字,标记了这个关键字的函数就是内联函数,不过只有标记“函数定义”时才是有效的,至于声明,标不标记inline都无所谓。由于内联函数和“宏”有点类似,而宏经常放在.h中,所以我们一般习惯于将“内联函数”的定义放在.h中,不过如果将函数放在.h中,以后可能会报重复定义的错误,所以我们往往需要将其修饰为static。

代码演示

inline.h

 #ifndef INLINE_H
#define INLINE_H static inline int my_max(int a, int b) //inline只有修饰函数定义时才有效
{
a *= ;
b /= ;
return (a>b) ? a : b; //找出最大值
} #endif

main.c

 #include <stdio.h>
#include "inline.h" int main(void)
{
int ret = ;
ret = my_max( ,);
printf("ret = %d\n", ret);
return ;
}

凡是要使用这个内联函数的.c,只需要包含这个.h即可。不过大家要小心,如果.c恰好有定义与“内联函数”同名的函数的话,不管这个函数普通函数还是“内联函数”,编译会报错的,所以编程时不要编写与内联函数同名的函数。

检查预编译后的.i文件

 inline int my_max(int a, int b)  //预编译后,这个玩意任然还在
{
a *= ;
b /= ; return (a > b) ? a : b;
} int main(void)
{
int ret = ; int (*funp) = my_max; ret = funp(, ); return ;
}

预编译后内联函数仍然还在,所以内联函数这玩意并不在“预编译阶段”被处理。

内联函数只是建议进行替换

指定了内联函数后,只是建议进行替换,到底会不会进行替换,这个需要看编译器,如果编译器判断存在如下情况的话,就算指定了inline关键字,但是编译器也只当做是普通函数。

函数代码量很大,不够简短,代码超过5句以上时,就不简短了

不是通过函数名调用的,而是通过“函数指针”来调用的

 int (*funp) = my_max;
ret = funp(, );

函数是递归

包含switch、while、for、do while等

编译时无法识别inline关键字,怎么办

在解释原因之前,先说说c标准。

c标准

为了标准化c语法,所以制定了C标准,目前的c标准有4个版本
c89:1989年制定
c90:1990年制定
c99:1999年制定
c11:2011年制定

其实中间还有c94/c95标准,不过这两个可以忽略。c89和c90其实是一个标准,为什么一个标准有两个名字,这个是由于历史原因导致的。

一般来说,制定新标准时,一些旧的语法可能会被修改或者抛弃,然后再添加一些新的语法特性,至于inline则是从c99才开始支持的语法特性,所以目前只有c99/c11才支持inline。

为什么有些编译器在编译内联函数时,会有问题呢

说明你的编译器版本比较老,老版本默认是按照c89/90去编译的,自然不认识inline,所以我们需要给gcc指定-std=c99或者-std=c11选项,明确的告诉编译器,请使用c99、c11标准去编译c程序,这是就没问题了。

gcc a.c -std=c99  //或者c11

推荐使用gcc --version查一下版本号,我的Ubuntu gcc版本号gcc version 5.4.0 20160609,是支持inline的

C++——inline function的更多相关文章

  1. 什么是内联函数(inline function)

    In C, we have used Macro function an optimized technique used by compiler to reduce the execution ti ...

  2. 内联扩展 inline expansion An Inline Function is As Fast As a Macro 与宏的比较

    让编译器直接将完整的函数体插入到每一个调用该函数的地方,从而提高函数调用的运行速度. 优秀的JIT编译器会通过侦测运行信息,仅将需要频繁运行的瓶颈部分进行编译,从而大大削减编译所需的时间. 而且,利用 ...

  3. [C++] inline function

    trap #define GET3(N)  N*N*N GET3(1+2) :  1+2*1+2*1+2 = 7

  4. About Why Inline Member Function Should Defined in The Header File

    About why inline member function should defined in the header file. It is legal to specify inline on ...

  5. Difference between Stored Procedure and Function in SQL Server

    Stored Procedures are pre-compile objects which are compiled for first time and its compiled format ...

  6. C++ inline weak symbol and so on

    关于inline这个关键字,听到强调得最多的是,它只是一种对于编译器的建议,而非强制执行的限定. 但事实上,即使这个优化最终由于函数太过复杂的原因没有达成,加上inline关键字(还有在类定义中直接定 ...

  7. Inline functions

    Problems: (Page 372) There are two problems with the use of proprocessor macros in C++. The first is ...

  8. c++virtual inline 是否冲突

    关于inline关键字:effective c++ item33:明智运用inlining.说到:inline指令就像register指令一样,只是对编译器的一种提示,而不是一个强制命令,意思是编译器 ...

  9. Understanding JavaScript Function Invocation and "this"

    Understanding JavaScript Function Invocation and "this" 11 Aug 2011 Over the years, I've s ...

随机推荐

  1. 关于python中的路径

    如果在train.py中调用了1.py中的A方法,则A中的相对路径按照train.py来写!

  2. Speech Recognition Java Code - HMM VQ MFCC ( Hidden markov model, Vector Quantization and Mel Filter Cepstral Coefficient)

    Hi everyone,I have shared speech recognition code inhttps://github.com/gtiwari333/speech-recognition ...

  3. JS的slice、substring、substr字符串截取

    JS中截取一个字符串的三种方法:字符串.slice(开始索引,结束索引)字符串.substring(开始索引,结束索引)字符串.substr(开始索引,截取的长度) 如果需要截取到该字符串的最后,可以 ...

  4. Ubuntu 新装服务器部署流程

    1.设定时区 rm -f /etc/localtime cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 2.配置apt-get源 sed -i ...

  5. NGUI无法显示

    早上起来发现 ,NGUI无法显示,后来发现是场景Camera的 depth =0 : 要设置depth=-1. 原来相机之间也有渲染层级

  6. Eclipse导war包忽略node_modules等文件

    window7环境下,选择project->Properties->如下图

  7. ztree节点名称排序

    // result 为后台返回的集合,在渲染tree前的数据 result = result.sort(function (a, b) { // 判断前面一个是字母,后面一个不是字母,那么不换位置,返 ...

  8. a标签添加移除事件及开启禁用事件

    一.添加移除点击事件 <script type="text/javascript" src="jquery.min.js"></script& ...

  9. BOOT目录磁盘占用满处理

    背景:Ubuntu:16.04 查看已安装启动镜像 dpkg --get-selections |grep linux-image 这里会列出目前已经安装的启动镜像,一般分两种,一种状态为“insta ...

  10. nmcli简单使用

    nmcli connection 查看所有网卡信息 nmcli connection show ens33 查看网卡具体信息 nmcli connection reload 是配置文件生效