内联函数定义
inline关键字是C99标准的型关键字,其作用是将函数展开,把函数的代码复制到每一个调用处。这样调用函数的过程就可以直接执行函数代码,而不发生跳转、压栈等一般性函数操作。可以节省时间,也会提高程序的执行速度。
为什么需要内联函数
在C语言中,如果一些函数被频繁的调用,不断地用函数入栈,即函数栈,则会造成栈空间或者栈内存的大量消耗,为了解决这个问题,特别的引入了inline关键字,表示为内联函数。
栈空间指的是函数内数据的内存空间,在一个系统下,栈空间的资源是有限的,假如频繁大量的使用就会因栈空间的不足而导致出错,函数的死循坏递归调用的最终结果就是导致栈内存空间的枯竭。
#include <stdio.h>//函数定义为inline即:内联函数
inline char* dbtest(int a)
{
return (i % 2 > 0) ? "奇" : "偶";
}
int main(){
int i = 0;
for (i=1; i < 100; i++) {
printf("i:%d 奇偶性:%s /n", i, dbtest(i));
}
}
上面的例子就是标准的内联函数的用法,使用inline修饰带来的好处我们表面看不出来,其实,在内部的工作就是在每个for循环的内部任何调用dbtest(i)的地方都换成了(i % 2 > 0) ? "奇" : "偶",这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。
内联函数注意事项
- 关键字inline必须与函数的定义体放在一起,才能使函数成为内联函数,仅仅将inline放在函数声明前面不起作用
如下风格的函数fun则成为内联函数:
void fun(int x, int y);inline void fun(int x, int y) //inline与函数的定义放在一起{ }
- 关键字inline的使用是有所限制的
inline只适合函数体内代码比较简单的函数使用,不能包含复杂的结构控制语句,例如while、switch,并且内联函数本身不能是直接递归函数(函数内部调用自己的函数)。
- inline仅是一个对编译器的建议
inline函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。
- 建议:inline函数的定义放在头文件中
其次,因为内联函数要在调用点展开,所以编译器必须随处可见内联函数的定义,要不然就成了非内联函数的调用了。所以,这要求每个调用了内联函数的文件都出现了该内联函数的定义。
因此,将内联函数的定义放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦。
声明跟定义要一致:如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为。如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定。所以,最好将内联函数定义放在头文件中。
- static和inline联合使用
static是静态修饰符,由其关键字修饰的变量会保存到全局数据区,对于普通的局部变量或者全局变量,都是由系统自动分配内存的,并且当变量离开作用域的时候释放掉,而使用static关键字来修饰,只有当程序结束时候才会释放掉,使用static inline修饰时,函数仅在文件内部可见,不会污染命名空间,另外,函数在运行过程中也会分配内存空间,但是由于static的存在,就和修饰变量类似,它只会开辟一块内存空间。
内联函数优缺点
- 普通函数在调用过程中,会对寄存器中内容进行上下文切换(push和pop操作),而内联函数则不需要,所以普通函数相比内联函数,耗时要多一些。
- 当函数使用次数比较多的时候,内联函数在每个调用的地方都会被展开,所以导致固件大小会变大,同一段代码会多次重复出现在固件中。而普通函数则没有此问题,不管调用的函数的次数多少,函数在固件中均只占用一处,空间利用率较高。inline函数其实就是空间换时间
inline 和宏的区别
虽然inline函数和带参数的宏很像,但是在使用方法上和宏还是有很大区别的:
| inline()函数 | 带参数的宏 | |
|---|---|---|
| 展开的时机 | 在编译的时候展开,因此inline关键字是一个编译关键字 | 在预处理时展开,因此#define关键字是一个预处理关键字 |
| 参数类型检查 | inline()函数是一中函数,会进行严格的参数类型检查 | 不会检查参数类型,只是做简单的字符串替换,因此在使用带参数的宏时会有一些副作用,编写程序是要人为预防 |
| 是否允许有复杂语句 | 不允许出现复杂语句,如果出现复杂语句,该函数将不会展开,例如递归,大型循环等 | 对此不做要求。宏只是做字符串替换操作,而不了解语句的含义 |
| 是否一定被展开 | 不一定,是否展开由编译器决定 | 一定,只要使用了宏就可以保证被展开 |
| 接口封装 | 是 | 否 |
| 是否支持调试 | 是 | 否 |
总结
- 内联函数相比宏函数,会进行语法检查。宏函数是在预处理阶段生效,内联函数是在编译阶段进行语法检查然后替换。
- 内联函数相比普通函数,少了上下文切换的步骤所以执行会更快一些。
- 内联函数被多次调用,会使固件大小膨胀,内联函数的高速是以空间来换时间。
- 内联函数不可递归。
- 如果函数内容太过于复杂,编译器会忽略inline关键字,把他当成普通函数来处理。
遇到 undefined reference to ‘add’ 错误,通常是因为 内联函数的定义没有被编译器正确识别,结合你的代码场景,可能有两个原因及对应解决办法:
原因1:内联函数未加 static,且未在头文件中定义
你的代码中 add 和 Print 是内联函数,但:
·
若它们在 .c 文件中定义(而非头文件),且没有 static 关键字,编译器可能不会生成独立的函数符号(内联函数默认不会生成符号表项)。
·
当其他文件调用这些函数时,链接器找不到对应的符号,就会报 undefined reference。
解决办法:根据使用场景选择以下方案
方案1:函数仅在当前 .c 文件中使用 → 加 static
在 inline 前添加 static,限制函数作用域为当前文件,编译器会确保内联展开或生成静态符号:
// 在 .c 文件中定义(仅当前文件使用)
static inline int add(int a, int b) { return a + b; }
static inline void Print() { printf(“*******************\n”); }
✅ 原理:static inline 会让编译器在每个使用该函数的 .c 文件中生成独立副本,避免跨文件链接问题。
方案2:函数需在多个 .c 文件中使用 → 放到头文件中
若其他 .c 文件也要调用 add 或 Print,必须将内联函数的完整定义(而非声明)放在 头文件(如 utils.h)中:
// utils.h(供多个 .c 文件包含)
#ifndef UTILS_H
#define UTILS_H
#include
inline int add(int a, int b) { return a + b; } // 完整定义
inline void Print() { printf(“*******************\n”); }
#endif
✅ 原理:C 语言规定,跨文件使用的内联函数必须在头文件中定义,编译器才能在每个包含该头文件的 .c 中内联展开。
验证是否生效
修改后重新编译,若仍报错,可通过以下命令查看符号表,确认 add 是否被正确生成:
nm -C test.o | grep add # 若显示 “t add”(小写 t 表示静态符号)或无输出(已完全内联),则正常
一句话总结
内联函数要么 static inline 放在 .c 文件(仅当前文件用),要么 inline 放在头文件(多文件共用),否则链接器会找不到函数定义~