补充对 inline 关键字的说明.

Signed-off-by: ithink.chan <chenyang@autoai.com>
This commit is contained in:
ithink.chan 2020-04-03 13:54:14 +08:00
parent aa2e9a9adb
commit f678d4abef
1 changed files with 198 additions and 0 deletions

View File

@ -0,0 +1,198 @@
# 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中定义的全局的fooint)函数,所以对于上面的例子将会在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()
{
...;
}
```