199 lines
8.9 KiB
Markdown
199 lines
8.9 KiB
Markdown
|
# GCC inline 关键字说明
|
|||
|
|
|||
|
inline关键字在GCC参考文档中仅有对其使用在函数定义(Definition)上的描述,而没有提到其是否能用于函数声明(Declare).
|
|||
|
|
|||
|
*inline关键字不应出现在函数声明中。*
|
|||
|
|
|||
|
inline关键字仅仅是建议编译器做内联展开处理,而不是强制。在gcc编译器中,如果编译优化设置为O0,即使是inline函数也不会被内联展开,除非设置了强制内联(__attribute__((always_inline)))属性。对于可展开与必须当成函数的情形同时出现,则在展开处需展开,在当成函数调用处则当函数处理.
|
|||
|
|
|||
|
## static inline
|
|||
|
|
|||
|
gcc的static inline相对于static函数来说只是在调用时建议编译器进行内联展开;gcc不会特意为static inline函数生成独立的汇编码,除非出现了必须生成不可的情况(例如函数指针调用和递归调用);
|
|||
|
|
|||
|
1. 不展开:函数本身递归等;函数的地址被使用时(赋予函数指针)
|
|||
|
2. 展开: gcc会在其调用处将其汇编码展开编译而不为这个函数生成独立的汇编码.
|
|||
|
|
|||
|
## inline
|
|||
|
|
|||
|
gcc的inline更容易理解:可以认为它是一个普通的全局函数加上了inline的属性。即在其定义所在文件内,它的行为和static inline一致:在能展开的时候会被内联展开编译。但是为了能够在文件外调用它,gcc一定会为它生成一份独立的汇编码,以便在外部进行调用。
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* @file foo.c
|
|||
|
* @brief example
|
|||
|
*/
|
|||
|
/*这里定义一个inline的函数foo()*/
|
|||
|
inline foo(void)
|
|||
|
{
|
|||
|
/*编译器在本文件内对其所调用处进行展开*/
|
|||
|
/*但同时也会像非inline函数一样为foo()生成独立的汇编码,以便文件外的函数调用foo();*/
|
|||
|
}
|
|||
|
|
|||
|
void func1()
|
|||
|
{
|
|||
|
/*同文件内foo()可能被编译器内联展开编译而不是直接call上面生成的汇编码*/
|
|||
|
foo();
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* @file bar.c
|
|||
|
* @brief example
|
|||
|
*/
|
|||
|
/*而在另一个文件里调用foo()的时候,则直接call的是上面文件内生成的汇编码:*/
|
|||
|
extern foo();//声明foo(),注意不能在声明内带inline关键字
|
|||
|
void func2()
|
|||
|
{
|
|||
|
/*这里就会直接call在foo.c内为foo()函数生成的汇编码了*/
|
|||
|
foo();
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
gcc的inline函数相对于普通的extern函数来说只是在同一个文件内调用时建议编译器进行内联展开;gcc一定回为inline函数生成一份独立的汇编码以供外部文件调用。在其它文件看来,这个inline函数和普通的extern函数无异;gcc的inline函数是全局性的:在文件内可以作为一个内联函数被内联展开,而在文件外可以调用它。
|
|||
|
|
|||
|
gcc的static inline和inline比较容易理解,可以认为是对普通函数添加可内联的属性。
|
|||
|
|
|||
|
## extern inline
|
|||
|
|
|||
|
gcc的extern inline十分古怪:一个extern inline函数只会被内联,而绝不会生成独立的汇编码!!!如果某处必须将其当成普通的函数,则此时对此函数的调用会被处理成一个外部引用。此外,extern inline 的函数允许和外部函数重名,即在存在一个外部定义的全局库函数的情况下,再定义一个同名的extern inline函数也是合法的。
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* @file foo.c
|
|||
|
* @brief example
|
|||
|
*/
|
|||
|
extern inline int foo(int a)
|
|||
|
{
|
|||
|
printf("%d\n",-a);
|
|||
|
return -a;
|
|||
|
}
|
|||
|
void func1()
|
|||
|
{
|
|||
|
......;
|
|||
|
a = foo(a); //*******1
|
|||
|
p_foo = foo; //*******2
|
|||
|
b = p_foo(b); //*******3
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* @file foo2.c
|
|||
|
* @brief example
|
|||
|
*/
|
|||
|
int foo(int a)
|
|||
|
{
|
|||
|
printf("%d\n",a);
|
|||
|
return a;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
在这个文件内,gcc不会生成foo函数的汇编码。
|
|||
|
|
|||
|
在func1中的1处,编译器会将上面定义的foo函数在这里内联展开编译,其行为类似普通的inline函数,因为这样的调用能够进行内联处理。
|
|||
|
|
|||
|
在func1中的2处应用了名称为foo的函数的地址,但是由于编译器绝不会为extern inline函数生成独立的汇编码,所以在这种非要取得函数地址的情况下,编译其只能将其处理为外部引用,在链接的时候链接到外部的foo函数(填写外部的函数地址)。这时,如果外部没有定义全局的foo函数的话链接时将产生foo函数位定义的错误。由于在func2.c中定义的全局的foo(int)函数,所以对于上面的例子,将会在1处使得a=-a,因为其内联了foo.c内的foo函数; 在3处使得b=b,因为去实际上调用的是foo2.c里面的foo函数!
|
|||
|
|
|||
|
## extern inline 的价值
|
|||
|
|
|||
|
第一:其行为可以像宏一样,可以在文件内用extern inline定义的版本取代外部定义的库函数(前提是文件内对其的调用不能出现无法内联的情况);
|
|||
|
|
|||
|
第二:它可以让一个库函数在能够被内联时尽可能被内联使用,example:
|
|||
|
|
|||
|
在一个库函数的c文件内,定义一个普通版本的库函数libfunc:
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* @file lib.c
|
|||
|
* @brief example
|
|||
|
*/
|
|||
|
void libfunc(void)
|
|||
|
{
|
|||
|
....;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
然后再在其头文件内,定义(注意不是声明!)一个实现相同的extern inline的版本:
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* @file lib.h
|
|||
|
* @brief example
|
|||
|
*/
|
|||
|
extern inline libfunc(void)
|
|||
|
{
|
|||
|
....;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
那么在别的文件要使用这个库函数的时候,只要include进lib.h,在能内联展开的地方,编译器都会使用头文件内extern inline 的版本来展开。而在无法展开的时候(函数指针引用等情况),编译器就会引用lib.c中的那个独立编译的普通版本。即看起来似乎是个可以在外部被内联的函数一样,所以这应该是gcc的extern inline意义的由来。
|
|||
|
|
|||
|
但是应当注意这样使用的代价:c文件中的全局函数的实现必须和同文件内extern inline版本的实现完全相同。否则就会出现前面所举例子中直接内联和间接调用时函数行为不一致的问题。
|
|||
|
|
|||
|
gcc绝不会为extern inline的函数生成独立的汇编码;extern inline函数允许和全局函数重名,可以在文件范围内替代外部定义的全局函数;使用extern inline时必须给予文档注释!!!!
|
|||
|
|
|||
|
## C99 的 inline
|
|||
|
|
|||
|
1. static inline(同gcc 的static inline)
|
|||
|
2. extern inline (无说明)
|
|||
|
3. inline:如果一个inline函数在文件范围内没有被声明为extern的话,这个函数在文件内的表现就和gcc的extern inline相似:在文件内调用时**允许**编译器使用文件内定义的这个内联版本,但同时也**允许**外部存在同名的全局函数。但是C99没有明确的指出编译器是否必须在文件内使用这个inline的版本,而是由编译器的厂家自己来决定。如果在文件内把这个inline函数声明为extern,则这个inline函数的行为就和gcc的inline一致了:这个函数即成为一个"external definition"(可以简单理解为全局函数):可以在外部被调用,并且在程序内仅能存在一个这样的名字。
|
|||
|
|
|||
|
举例说明C99中inline的特性:
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* @brief example
|
|||
|
*/
|
|||
|
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的声明:
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* @file foo.h
|
|||
|
* @brief example
|
|||
|
*/
|
|||
|
extern foo();
|
|||
|
```
|
|||
|
|
|||
|
而在定义inline函数的c文件内include这个头文件:
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* @file foo.c
|
|||
|
* @brief example
|
|||
|
*/
|
|||
|
#include "foo.h"
|
|||
|
inline void foo()
|
|||
|
{
|
|||
|
...;
|
|||
|
}
|
|||
|
```
|