转之前我用Docbook写的一篇关于C语言inline关键字使用的文章。唉,要是能用docbook直接写Blog就行了。用得越多发现Docbook这个东西真是越好用啊~~html
本文介绍了GCC和C99标准中inline使用上的不一样之处。inline属性在使用的时候,要注意如下两点:ide
从inline的做用来看,其放置于函数声明中应当也是毫无做用的:inline只会影响函数在translation unit(能够简单理解为C源码文件)内的编译行为,只要超出了这个范围inline属性就没有任何做用了。因此inline关键字不该该出如今函数声明中,没有任何做用不说,有时还可能形成编译错误(在包含了sys/compiler.h的状况下,声明中出现inline关键字的部分一般没法编译经过);函数
1. GCC的inline优化
gcc对C语言的inline作了本身的扩展,其行为与C99标准中的inline有较大的不一样。编码
1.1. static inlinespa
GCC的static inline定义很容易理解:你能够把它认为是一个static的函数,加上了inline的属性。这个函数大部分表现和普通的static函数同样,只不过在调用这种函数的时候,gcc会在其调用处将其汇编码展开编译而不为这个函数生成独立的汇编码。除了如下几种状况外:指针
static inline函数和static函数同样,其定义的范围是local的,便可以在程序内有多个同名的定义(只要不位于同一个文件内便可)。htm
|
注意递归 |
gcc的static inline的表现行为和C99标准的static inline是一致的。因此这种定义能够放心使用而没有兼容性问题。ip 要点:
|
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.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函数的地址。可是注意:编译器是绝对不会为externinline函数生成独立汇编码的!因此在这种非要个函数地址不可的状况下,编译器不得不将其处理为外部引用,在连接的时候连接到外部的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这样的描述,因此不建议你们使用externinline,除非你明确理解了这种用法的意义而且有充足的理由使用它! 要点:
|
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 inlinedefinition 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的用法。