inline

来源:互联网 发布:java调用命令行 编辑:程序博客网 时间:2024/06/06 06:16
在c中,为了解决一些频繁调用的小涵数大量消耗栈空间或是叫栈内存的问题,特别的引入了inline修饰符,表示为内联涵数。
可能说到这里,很多人还不明白什么是栈空间,其实栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,涵数的死循环递归调用的最终结果就是导致栈内存空间枯竭。


内敛函数的功能和宏定义(#define )差不多,即在编译阶段进行替换,减少运行时间,提高运行效率。区别是宏定义没有语法检查,内敛函数有语法检查,更不会出错!


下面我们来看一个例子
#include <stdio.h>

inline char* dbtest(int a); //函数原形声明为inline即:内联涵数

int main()
{
int i = 0;

for (i=1;i<=10;i )
{
   printf("%d is %s\n",i,dbtest(i));
}

return 0;
}

char* dbtest(int a)//这里不用再次inline,当然加上inline也是不会出错的
{
return (a%2>0)?"奇":"偶";
}


上面的例子就是标准的内联涵数的用法,使用inline修饰带来的好处我们表面看不出来,其实在内部的工作就是在每个for循环的内部任何调用 dbtest(i)的地方都换成了(i%2>0)?"奇":"偶"这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。

说到这里很多人可能会问,既然inline这么好,还不如把所谓的函数都声明成inline,嗯,这个问题是要注意的,inline的使用是有所限制的,inline只适合涵数体内代码简单的涵数使用,不能包含复杂的结构控制语句例如while switch,并且不能内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。



inline属性在使用的时候,要注意以下两点:inline关键字在GCC参考文档中仅有对其使用在函数定义(Definition)上的描述,而没有提到其是否能用于函数声明(Declare)。 

从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.2. inline 

相对于C99的inline来说,GCC的inline更容易理解:可以认为它是一个普通全局函数加上了inline的属性。即在其定义所在文件内,它的表现和static inline一致:在能展开的时候会被内联展开编译。但是为了能够在文件外调用它,gcc一定会为它生成一份独立的汇编码,以便在外部进行调用。即从文件外部看来,它和一个普通的extern的函数无异。举个例子: 

foo.c: /* 这里定义了一个inline的函数foo() */ inline foo() { 编译器会像非inline函数一样为foo()生成独立的汇编码 } void func1() { foo(); 同文件内foo()可能被编译器内联展开编译而不是直接call上面生成的汇编码 } 而在另一个文件里调用foo()的时候,则直接call的是上面文件内生成的汇编码: bar.c: extern foo(); 声明foo(),注意不能在声明内带inline关键字 void func2() { foo(); 这里就是直接call在foo.c内为foo()函数生成的汇编码了 

虽然gcc的inline函数的行为很好理解,但是它和C99的inline是有很大差别的。请注意看后面对C99 inline的描述(第 2.2 节 “inline”),以及如何以兼顾GCC和C99的方式使用inline函数。 

要点: gcc的inline函数相对于普通extern函数来说只是在同一个文件内调用时建议编译器进行内联展开; gcc一定会为inline函数生成一份独立的汇编码,以便其在本文件之外被调用。在别的文件内看来,这个inline函数和普通的extern函数无异; gcc的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函数的应用范围十分狭窄,而且行为比较奇怪,不建议使用



说到这里我们不得不说一下在c语言中广泛被使用的#define语句,是的define的确也能够做到inline的这些工作,但是define是会产生副作用的,尤其是不同类型参数所导致的错误,由此可见inline有更强的约束性和能够让编译器检查出更多错误的特性,在c 中是不推荐使用define的。
原创粉丝点击