inline内敛函数

来源:互联网 发布:创业软件复牌消息 编辑:程序博客网 时间:2024/03/29 15:06

宏与内联函数(面试常考)收藏

第一部分:宏
为什么要使用宏呢?
因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。
而宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。
但是宏也有很多的不尽人意的地方。
1、宏不能访问对象的私有成员。
2、宏的定义很容易产生二意性。
我们举个例子:
#define square(x) (x*x)
我们用一个数字去调用它,square(5),这样看上去没有什么错误,结果返回25,是正确的,但是如果我们用squre (5+5)去调用的话,我们期望的结果是100,而宏的调用结果是(5+5*5+5),结果是35,这显然不是我们要得到的结果。避免这些错误的方法,一是给宏的参数都加上括号。
#define square(x) ((x)*(x))

第二部分:内联函数
从上面的阐述,可以看到宏有一些难以避免的问题,怎么解决呢?
内联函数是代码被插入到调用者代码处的函数。如同 #define 宏,内联函数通过避免被调用的开销来提高执行效率,尤其是它能够通过调用(“过程化集成”)被编译器优化。
内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
声明内联函数看上去和普通函数非常相似:
void f(int i, char c);
当你定义一个内联函数时,在函数定义前加上 inline 关键字,并且将定义放入头文件:
inline void f(int i, char c)
{
// ...
}
内联函数必须是和函数体申明在一起,才有效。
像这样的申明inline function(int i)是没有效果的,编译器只是把函数作为普通的函数申明,我们必须定义函数体。
inline int function(int i) {return i*i;}
这样我们才算定义了一个内联函数。我们可以把它作为一般的函数一样调用。但是执行速度确比一般函数的执行速度要快。
当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。
有上面的两者的特性,我们可以用内联函数完全取代预处理宏。

*********************************************************************************************************************************

从inline的作用来看,其放置于函数声明中应当也是毫无作用的:inline只会影响函数在translation unit(可以简单理解为C源码文件)内的编译行为,只要超出了这个范围inline属性就没有任何作用了。所以inline关键字不应该出现在函数声明中,没有任何作用不说,有时还可能造成编译错误(在包含了sys/compiler.h的情况下,声明中出现inline关键字的部分通常无法编译通过);
inline关键字仅仅是建议编译器做内联展开处理,而不是强制。在gcc编译器中,如果编译优化设置为-O0,即使是inline函数也不会被内联展开,除非设置了强制内联(__attribute__((always_inline)))属性。

1. GCC的inline
gcc对C语言的inline做了自己的扩展,其行为与C99标准中的inline有较大的不同。

1.1. static inline
GCC的static inline定义很容易理解:你可以把它认为是一个static的函数,加上了inline的属性。这个函数大部分表现和普通的static函数一样,只不过在调用这种函数的时候,gcc会在其调用处将其汇编码展开编译而不为这个函数生成独立的汇编码。除了以下几种情况外:
函数的地址被使用的时候。如通过函数指针对函数进行了间接调用。这种情况下就不得不为static inline函数生成独立的汇编码,否则它没有自己的地址。
其他一些无法展开的情况,比如函数本身有递归调用自身的行为等。
static inline函数和static函数一样,其定义的范围是local的,即可以在程序内有多个同名的定义(只要不位于同一个文件内即可)。
 注意 
gcc的static inline的表现行为和C99标准的static inline是一致的。所以这种定义可以放心使用而没有兼容性问题。
要点:
gcc的static inline相对于static函数来说只是在调用时建议编译器进行内联展开;
gcc不会特意为static inline函数生成独立的汇编码,除非出现了必须生成不可的情况(如通过函数指针调用和递归调用);
gcc的static inline函数仅能作用于文件范围内。
1.3. extern inline
GCC的static inline和inline都很好理解:看起来都像是对普通函数添加了可内联的属性。但是这个extern inline就千万不能想当然地理解成就是一个extern的函数+inline属性了。实际上gcc的extern inline十分古怪:一个extern inline的函数只会被内联进去,而绝对不会生成独立的汇编码!即使是通过指针应用或者是递归调用也不会让编译器为它生成汇编码,在这种时候对此函数的调用会被处理成一个外部引用。另外,extern inline的函数允许和外部函数重名,即在存在一个外部定义的全局库函数的情况下,再定义一个同名的extern inline函数也是合法的。以下用例子具体说明一下extern inline的特点:
foo.c:
extern inline
int foo(int a)
{
    return (-a);
}
void func1()
{
    ...;
    a = foo(a);   ①
    p_foo = foo;  ②
    b = p_foo(b); ③
}
在这个文件内,gcc不会生成foo函数的汇编码。在func1中的调用点①,编译器会将上面定义的foo函数在这里内联展开编译,其表现类似于普通inline函数。因为这样的调用是能够进行内联处理的。而在②处,引用了foo函数的地址。但是注意:编译器是绝对不会为extern inline函数生成独立汇编码的!所以在这种非要个函数地址不可的情况下,编译器不得不将其处理为外部引用,在链接的时候链接到外部的foo函数去(填写外部函数的地址)。这时如果外部没有再定义全局的foo函数的话就会在链接时产生foo函数未定义的错误。
假设在另一个文件里面也定义了一个全局函数foo:
foo2.c:
int foo(int a)
{
    return (a);
}
那么在上面那个例子里面,后面一个对foo函数地址的引用就会在链接时被指到这个foo2.c中定义的foo函数去。也就是说:①调用foo函数的结果是a=-a,因为其内联了foo.c内的foo函数;而③调用的结果则是b=b,因为其实际上调用的是foo2.c里面的foo函数!
extern inline的用法很奇怪也很少见,但是还是有其实用价值的。第一:它可以表现得像宏一样,可以在文件内用extern inline版本的定义取代外部定义的库函数(前提是文件内对其的调用不能出现无法内联的情况);第二:它可以让一个库函数在能够被内联的时候尽量被内联使用。举个例子:
在一个库函数的c文件内,定义一个普通版本的库函数libfunc:
lib.c:
void libfunc()
{
    ...;
}
然后再在其头文件内,定义(注意不是声明!)一个实现相同的exterin inline的版本:
lib.h:
extern inline libfunc()
{
    ...;
}
那么在别的文件要使用这个库函数的时候,只要include了lib.h,在能内联展开的地方,编译器都会使用头文件内extern inline的版本来展开。而在无法展开的时候(函数指针引用等情况),编译器就会引用lib.c中的那个独立编译的普通版本。即看起来似乎是个可以在外部被内联的函数一样,所以这应该是gcc的extern inline意义的由来。 但是注意这样的使用是有代价的:c文件中的全局函数的实现必须和头文件内extern inline版本的实现完全相同。否则就会出现前面所举例子中直接内联和间接调用时函数表现不一致的问题。
 重要 
gcc的extern inline函数的用法相当奇怪,使用的范围也非常狭窄:几乎没有什么情况会需要用它。 C99中,也没有关于extern inline这样的描述,所以不建议大家使用extern inline,除非你明确理解了这种用法的意义并且有充足的理由使用它!
要点: gcc绝对不会为extern inline的函数生成独立汇编码
extern inline函数允许和全局函数重名,可以在文件范围内替代外部定义的全局函数
extern inline函数的应用范围十分狭窄,而且行为比较奇怪,不建议使用