您的位置:首页 > 编程语言 > C语言/C++

内联函数学习

2016-01-22 21:39 183 查看
 

内联函数是指用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。

 

定义:内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。

 

动机:内联扩展是用来消除函数调用时的时间开销。它通常用于频繁执行的函数。 一个小内存空间的函数非常受益。如果没有内联函数,编译器可以决定哪些函数内联。 程序员很少或没有控制哪些只能是内联的,哪些不是。 给这种控制程度,作用是程序员可以选择内联的特定应用 。

 

使用内联函数的时候要注意:

1.递归函数不能定义为内联函数

2.内联函数一般适合于不存在while和switch等复杂的结构且只有1~5条语句的小函数上,否则编译系统将该函数视为普通函数。

3.内联函数只能先定义后使用,否则编译系统也会把它认为是普通函数。

4.对内联函数不能进行异常的接口声明。

 

内联函数的使用及注意点

内联函数并不总是内联。 Inline function是在C++中引入的一种机制,它可以拓展函数代码,避免调用函数的额外开销。在Linux环境下,gcc编译选项必须加上优化选项才能使inline有效。

 

inline与static的关系

在这儿有一个比较详细的分析:http://www.cnblogs.com/xkfz007/articles/2370640.html

 

内联函数(inline)机制与陷阱

内联机制被引入C++作为对宏(Macro)机制的改进和补充(不是取代)。内联函数的参数传递机制与普通函数相同。但是编译器会在每处调用内联函数的地方将内联函数的内容展开。这样既避免了函数调用的开销又没有宏机制的前三个缺陷。

 

但是程序代码中的关键字"inline"只是对编译器的建议:被"inline"修饰的函数不一定被内联(但是无"inline"修饰的函数一定不是)。

 

许多书上都会提到这是因为编译器比绝大多数程序员都更清楚函数调用的开销有多大,所以如果编译器认为调用某函数的开销相对该函数本身的开销而言微不足道或者不足以为之承担代码膨胀的后果则没必要内联该函数。这当然有一定道理,但是按照C、C++一脉相承的赋予程序员充分自由与决定权的风格来看,理由还不够充分。我猜想最主要的原因是为了避免编译器陷入无穷递归。如果内联函数之间存在递归调用则可能导致编译器展开内联函数时陷入无穷递归。有时候函数的递归调用十分隐蔽,程序员并不容易发现,所以简单起见,将内联与否的决定权交给编译器。

 

另一种不被内联的情况是使用函数指针来调用内联函数。

对于C++中内联机制的一个常见误解是:关键字"inline"只是对编译器的建议,如果编译器发现指定的函数不适合内联就不会内联;所以即使内联使用的不恰当也不会有任何副作用。这句话只对了一半,内联使用不恰当是会有副作用的:会带来代码膨胀,还有可能引入难以发现的程序臭虫。

根据规范,当编译器认为希望被内联的函数不适合内联的时候,编译器可以不内联该函数。但是不内联该函数不代表该函数就是一个普通函数了,从编译器的实际实现上来讲,内联失败的函数与普通函数是有区别的:

(1)普通的函数在编译时被单独编译一个对象,包含在相应的目标文件中。目标文件链接时,函数调用被链接到该对象上。

(2)若一个函数被声明成内联函数,编译器即使遇到该函数的声明也不会为该函数编译出一个对象,因为内联函数是在用到的地方展开的。可是若在调用该内联函数的地方发现该内联函数的不适合展开时怎么办?一种选择是在调用该内联函数的目标文件中为该内联函数编译一个对象。这么做的直接后果是:若在多个文件调用了内联失败的函数,其中每个文件对应的目标文件中都会包含一份该内联函数的目标代码。

如果编译器真的选择了上面的做法对待内联失败的函数,那么最好的情况是:没吃到羊肉,反惹了一身骚。即内联的好处没享受到,缺点却承担了:目标代码的体积膨胀得与成功内联的目标代码一样,但目标代码的效率确和没内联一样。

 

更糟的是由于存在多份函数目标代码带来一些程序臭虫。最明显的例子是:内联失败的函数内的静态变量实际上就不在只有一份,而是有若干份。这显然是个错误,但是如果不了解内幕就很难找到原因。

 

 

转:C语言inline详细讲解
本文介绍了GCC和C99标准中inline使用上的不同之处。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会在其调用处将其汇编码展开编译而不为这个函数生成独立的汇编码。除了以下几种情况外: 
(1)函数的地址被使用的时候。如通过函数指针对函数进行了间接调用。这种情况下就不得不为static
inline函数生成独立的汇编码,否则它没有自己的地址。 
(2)其他一些无法展开的情况,比如函数本身有递归调用自身的行为等。
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函数。 
要点: 
(1)gcc的inline函数相对于普通extern函数来说只是在同一个文件内调用时建议编译器进行内联展开; 
(2)gcc一定会为inline函数生成一份独立的汇编码,以便其在本文件之外被调用。在别的文件内看来,这个inline函数和普通的extern函数无异; 
(3)c的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函数的应用范围十分狭窄,而且行为比较奇怪,不建议使用
2. C99的inline
以下主要描述C99的inline与Gcc不同的部分。对于相同的部分请参考GCC
inline的说明。
2.1. static inline
同GCC的static inline(第 1.1
节 “static inline”)。
2.2. inline
C99的inline的使用相当令人费解。当一个定义为inline的函数没有被声明为extern的时候,其表现有点类似于gcc中extern
inline那样(C99里面这段描述有点晦涩,原文如下): 
If all of the file scope declarations for a function in a translation unit include the inline function specifier without extern, then the definition in that translation unit is an inline definition. An inline definition does not
provide an external definition for the function, and does not forbid an external definition in another translation unit. An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function
in the same translation unit. It is unspecified whether a call to the function uses the inline definition or the external definition.
即如果一个inline函数在文件范围内没有被声明为extern的话,这个函数在文件内的表现就和gcc的extern
inline相似:在本文件内调用时允许编译器使用本文件内定义的这个内联版本,但同时也允许外部存在同名的全局函数。只是比较奇怪的是C99居然没有指定编译器是否必须在本文件内使用这个inline的版本而是让编译器厂家自己来决定,相当模糊的定义。 
如果在文件内把这个inline函数声明为extern,则这个inline函数的表现就和gcc的inline一致了:这个函数即成为一个“external
definition”(可以简单理解为全局函数):可以在外部被调用,并且在程序内仅能存在一个这样名字的定义。 
下面举例说明C99中inline的特性:
inline double fahr(double t)

{

    return (9.0 * t) / 5.0 + 32.0;

}

inline double cels(double t)

{

    return (5.0 * (t - 32.0)) / 9.0;

}

extern double fahr(double);   ①

double convert(int is_fahr, double temp)

{

    return is_fahr ? cels(temp) : fahr(temp);   ②

}
在上面这个例子里,函数fahr是个全局函数:因为在①处将fahr声明为extern,因此在②处调用fahr的时候使用的一定是这个文件内所定义的版本(只不过编译器可以将这里的调用进行内联展开)。在文件外部也可以调用这个函数(说明像gcc的inline一样,编译器在这种情况下会为fahr生成独立的汇编码)。 
而cels函数因为没有在文件范围内被声明为extern,因此它就是前面所说的“inline
definition”,这时候它实际上仅能作用于本文件范围(就像一个static的函数一样),外部也可能存在一个名字也为cels的同名全局函数。在②处调用cels的时候编译器可能选择用本文件内的inline版本,也有可能跑去调用外部定义的cels函数(C99没有规定此时的行为,不过编译器肯定都会尽量使用文件内定义的inline版本,要不然inline函数就没有存在的意义了)。从这里的表现上看C99中未被声明为extern的
inline函数已经和gcc的extern inline十分相似了:本文件内的inline函数可以作为外部库函数的替代。 
 重要:C99 标准中的inline函数行为定义的比较模糊,并且inline函数有没有在文件范围内被声明为extern的其表现有本质不同。如果和gcc的
inline函数比较的话,一个被声明为extern的inline函数基本等价于GCC的普通inline函数;而一个没有被声明为extern的
inline函数基本等价于GCC的extern inline函数。 
因为C99的inline函数如此古怪,所以在使用的时候,建议为所有的inline函数都在头文件中创建extern的声明:
foo.h: 
extern foo();
而在定义inline函数的c文件内include这个头文件:
foo.c:

#include "foo.h"

inline void foo()

{

    ...;

}
这样无论是用gcc的inline规则还是C99的,都能得到完全相同的结果:foo函数会在foo.c文件内被内联使用,而在外部又可以像普通全局函数一样直接调用。
2.3. extern inline
C99没有见到extern inline的用法。
 
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ 内联函数