inline关键字

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

inline关键字
本⽂介绍了GCC和C99标准中inline使⽤上的不同之处。

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

从inline的作⽤来看,其放置于函数声明中应当也是毫⽆作⽤的:inline只会影响函数在translation unit(可以简单理解为C源码⽂件)内的编译⾏为,只要超出了这个范围inline属性就没有任何作⽤了。

所以inline关键字不应该出现在函数声明中,没有任何作⽤不说,有时还可能造成编译错误(在包含了sys/compiler.h的情况下,声明中出现inline关键字的部分通常⽆法编译通过);
2. 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函数相对于普通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函数的应⽤范围⼗分狭窄,⽽且⾏为⽐较奇怪,不建议使⽤
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那样,即如果⼀个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的;⽽⼀个没有被声明为extern的inline函数基本等价于GCC 的。

因为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的⽤法。

相关文档
最新文档