一、inline、static inline和extern inline区别

1、inline

函数被调用时,需要出栈入栈。当函数频繁被调用时,则不断地有函数出栈入栈,会造成栈空间或栈内存的大量消耗。所以引入了inline。

inline: 在函数定义中,函数返回类型前加上关键字inline,即可以把函数指定为内联函数。
内联函数: 建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展)。也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。有些类似于宏。

内联函数与宏调用的比较

  1. 宏调用并不执行类型检查,甚至连正常参数也不检查,但是函数调用却要检查。
  2. C语言的宏使用的是文本替换,可能导致无法预料的后果,因为需要重新计算参数和操作顺序。
  3. 在宏中的编译错误很难发现,因为它们引用的是扩展的代码,而不是程序员键入的。
  4. 许多结构体使用宏或者使用不同的语法来表达很难理解。内联函数使用与普通函数相同的语言,可以随意的内联和不内联。
  5. 内联代码的调试信息通常比扩展的宏代码更有用。

注意:

  1. 关键字inline必须与函数定义放在一起才能使函数成为内联,仅仅将inline放在函数声明前面不起任何作用。
  2. inline函数仅仅是一个建议,对编译器的建议,最后能否真正内联,还要看编译器,并不是说声明了内联就会内联,声明内联只是一个建议而已。
  3. inline的缺点是当函数被多次调用时,代码替换后,代码量增加,程序膨胀。所以要慎重使用!尤其要避免如递归,循环等。

2、static inline

c文件中的仅inline函数是不内联的,因为没有static,编译会认为它是全局的,因此像普通函数一样编译了。
加入static,这样内部调用函数时,会内联,而外部调用该函数时,则不会内联。

在调用这种函数的时候,gcc会在其调用处将其汇编码展开编译而不为这个函数生成独立的汇编码。除了以下几种情况外:

  1. 函数的地址被使用的时候。如通过函数指针对函数进行了间接调用。这种情况下就不得不为static inline函数生成独立的汇编码,否则它没有自己的地址。
  2. 其他一些无法展开的情况,比如函数本身有递归调用自身的行为等。

3、extern inline

  1. extern inline函数只会被内联进去,绝不会生成独立的汇编码。即使是通过指针应用或者是递归调用也不会让编译器为它生成汇编码。
  2. extern inline函数允许和外部函数重名,在存在一个外部定义的全局库函数的情况下,再定义一个同名的extern inline函数也是合法的。
    在这种时候对此函数的 调用会被处理成一个外部引用。另外,

当extern inline函数通过指针应用或者是递归调用时,编译器会将其处理为外部引用,在链接的时候链接到外部的同名函数中去(填写外部函数的地址)。这时如果外部没有全局的同名函数,就会在链接时产生函数未定义的错误。

注意:static inline首先我们必须认识到,这是一个static函数,也即函数是文件内链接的,定义放在头文件,即使多处包含也不会出现重定义问题,如果你将它放进源文件反而会出错,因为这时其他文件看不到定义,所以必须定义在头文件,然后在调用处将会做内敛展开。而extern inline首先函数是extern的,也就是说函数可能是从其他文件引入的,如果本文件看不到定义,则只会做普通函数调用,如果能看到定义,则可以尝试inline展开。

二、示例分析:

c文件中定义一个简单的inline函数

inline void swap(char *x, char *y){char t = *x; *x=*y; *y=t;}

编译的时候报错

gcc --std=c11 -Wall    -c -o permutation.o permutation.c
gcc   permutation.o  -lgmp -o permutation
permutation.o:permutation.c:(.text+0x92):对‘swap’未定义的引用
permutation.o:permutation.c:(.text+0x92):  截断重寻址至相符: R_X86_64_PC32 针对未定义的符号 swap
permutation.o:permutation.c:(.text+0xcb):对‘swap’未定义的引用
permutation.o:permutation.c:(.text+0xcb):  截断重寻址至相符: R_X86_64_PC32 针对未定义的符号 swap
collect2: 错误:ld 返回 1
make: *** [<内置>:permutation] 错误 1

# 编译器版本
$ gcc --version
gcc (GCC) 9.1.0
Copyright © 2019 Free Software Foundation, Inc.
本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保;
包括没有适销性和某一专用目的下的适用性担保。

解决办法

方法1. 开启 -O 优化

gcc -O   -c -o permutation.o permutation.c
gcc   permutation.o  -lgmp -o permutation

方法2. 添加非inline的原型声明

void swap(char *x, char *y);
inline void swap(char *x, char *y){char t = *x; *x=*y; *y=t;}
cc --std=c11 -Wall    -c -o permutation.o permutation.c
gcc   permutation.o  -lgmp -o permutation

方法3. 声明时同时使用static关键字

static inline void swap(char *x, char *y){char t = *x; *x=*y; *y=t;}
$ make permutation
gcc --std=c11 -Wall    -c -o permutation.o permutation.c
gcc   permutation.o  -lgmp -o permutation

原因

生成汇编代码,查看swap函数的情况,做成表格如下
在这里插入图片描述

总结

一开始出现编译错误的原因是对swap的调用是真的调用(没有转换成内嵌代码),同时还没有输出swap的对应的汇编代码。

C中定义内联函数要同时使用static inline修饰符,这样生成的代码开启优化选项后不输出汇编代码,直接内嵌调用(一般情况);如果添加非inline函数原型,相当于extern inline swap(){...},即使开启优化选项,也会生成相应的汇编代码,只不过本地调用直接内嵌(一般情况)。

C和C++不一样,C++的inline函数自带static属性,而C中的需要显式指定static才行。

完整代码

#include <stdio.h>
#include <string.h>
#define MX 20

// 输出给定子串的全排
void perm_recursive(char *s, int n);

char buf[MX] = "ABCD";
int main(void){
    perm_recursive(buf, strlen(buf));
    return 0;
}

inline void swap(char *x, char *y){char t = *x; *x=*y; *y=t;}

// 全排的递归实现
static void __perm(char *s, int n, int off){
    if (off == n-1){
        printf("%s", s);
        printf("\n");
    }else{
        for (int k = off; k<n; k++){
            swap(s+off, s+k);
            __perm(s, n, off+1);
            swap(s+off, s+k);
        }
    }

}
void perm_recursive(char *s, int n){
    if (n<1) return;
    __perm(s, n, 0);
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐