C 标准在 C99 中才提供对内联函数的支持, 但 gcc 却早已做到了这一点内联函数的作用是尽可能地让函数允许地快一些, 就如同宏一样本文将用实例来探讨 gcc 下的内联函数, 消除一些人们对它的疑惑需要说明的是, 本文所列出的所有代码均是在 64 位 ubuntu linux 15.04 下用 gcc4.9.2 编译
普通函数
我们先写一个普通的 C 程序(源文件为 main.c), 代码如下:
- #include < stdio.h > #include < stdlib.h > int add(int a, int b);
- int main(int argc, char * *argv) {
- printf("a + b = %d\n", add(1, 2));
- return (EXIT_SUCCESS);
- }
- int add(int a, int b) {
- return a + b;
- }
我们使用下列编译命令的到该程序的汇编代码:
- gcc main.c - S - o main.s
- .file "main.c".section.rodata.LC0: .string "a + b = %d\n".text.globl main.type main,
- @
- function main: .LFB0: .cfi_startproc pushq % rbp.cfi_def_cfa_offset 16.cfi_offset 6,
- -16 movq % rsp,
- %rbp.cfi_def_cfa_register 6 subq $16,
- %rsp movl % edi,
- -4( % rbp) movq % rsi,
- -16( % rbp) movl $2,
- %esi movl $1,
- %edi call add movl % eax,
- %esi movl $.LC0,
- %edi movl $0,
- %eax call printf movl $0,
- %eax leave.cfi_def_cfa 7,
- 8 ret.cfi_endproc.LFE0: .size main,
- . - main.globl add.type add,
- @
- function add: .LFB1: .cfi_startproc pushq % rbp.cfi_def_cfa_offset 16.cfi_offset 6,
- -16 movq % rsp,
- %rbp.cfi_def_cfa_register 6 movl % edi,
- -4( % rbp) movl % esi,
- -8( % rbp) movl - 4( % rbp),
- %edx movl - 8( % rbp),
- %eax addl % edx,
- %eax popq % rbp.cfi_def_cfa 7,
- 8 ret.cfi_endproc.LFE1: .size add,
- . - add.ident "GCC: (Ubuntu 4.9.2-10ubuntu13) 4.9.2".section.note.GNU - stack,
- "",
- @progbits
是的, 输出的汇编代码确实地有些长, 但为了能够看到代码的全貌, 我没有做删减同时也为了简单一些, 我没有使用其他编译选项来影响汇编代码的输出从汇编代码中第 21 行我们看到在 main 函数中调用了 add 函数
内联函数
内联函数的声明与定义
虽然看过很多有关内联函数的声明定义的文章, 甚至查阅了 gnu 的技术文档, 但仍然没有搞明白应该如何声明定义一个内联函数, 难道可以有多种方法吗?
- To declare a function inline, use the inline keyword in its declaration, like this:
- static inline int
- inc (int *a)
- {
- return (*a)++;
- }
上面这部分引用自 gnu 的技术文档从这段话来看, 只要在一个函数的声明部分添加上关键字 inline, 这样就可以让一个函数变成内联函数但经过我多次尝试发现, 其实如果在函数定义的时候加上关键字 inline 也是可以的同时我发现, 把关键字加到不同的位置, 产生的效果可能会有所不同并且关键字加在不同的位置, 可能会产生混淆, 在某个地方以为是以普通方式调用函数实际上却内联函数了, 或是以为采用的是内联函数的方式却以普通方式调用函数了因此, 我在这里做出一个约定: 本文所提到的内联函数, 其声明与定义所采用的修饰符是一致的也就是说, 内联函数的声明部分是把其函数头后面加上分号得到的如下:
- inline int add(int a, int b) __attribute__((always_inline));
- inline int add(int a, int b) {
- return a + b;
- }
需要注意的一点是, 关键字 inline 仅仅是让函数尽可能快地执行函数但具体是用内联方式还是普通方式执行函数, 这具体要看编译阶段, 编译器是如何决定的因此, 为了看到内联方式的效果, 我们在内联函数声明部分添加函数属性 attribute((always_inline)) (因网页编辑器的原因, 应该在 attribute 左右两边均加上两个下划线 '_')来强制编译器总是采用内联方式, 如上面的代码如果不添加该属性, 则仍然会以普通函数的方式调用该函数
另外, 还需要注意的一点是, 某些情况下就算我们并没有使用关键字 inline 来说明某个函数是内联函数, 但编译器仍然会以内联方式编译这个函数这种情况通常发生在函数比较短小, 逻辑比较简单, 并且采用优化方式编译如果这种情况破坏了我们的逻辑设计, 我们就需要使用函数属性 attribute((noinline)) 来告诉编译器, 这个函数不允许内联
inline 与 static inline 以及 extern inline
我们可以在某个函数的声明定义部分添加关键字 inline 使其变成内联函数但我们还可以另外再添加关键字 static 或 extern 产生不同效果的内联函数下面是三种内联函数的声明与定义:
- inline int add(int a, int b) __attribute__((always_inline));
- inline int add(int a, int b) {
- return a + b;
- }
- static inline int add(int a, int b) __attribute__((always_inline));
- static inline int add(int a, int b) {
- return a + b;
- }
- extern inline int add(int a, int b) __attribute__((always_inline));
- extern inline int add(int a, int b) {
- return a + b;
- }
下表是在遵循不同的标准下产生的效果:
标准 | inline | static inline | extern inline |
---|---|---|---|
c11 | 无定义、局部函数 | 无定义、局部函数 | 有定义、全局函数 |
c99 | 无定义、局部函数 | 无定义、局部函数 | 有定义、全局函数 |
c89 | 语法错误 | 语法错误 | 语法错误 |
gnu11 | 无定义、局部函数 | 无定义、局部函数 | 有定义、全局函数 |
gnu99 | 无定义、局部函数 | 无定义、局部函数 | 有定义、全局函数 |
gnu89 | 有定义、全局函数 | 无定义、局部函数 | 无定义、局部函数 |
无定义: 编译器没有为内联函数单独生成指令, 即不能以函数调用的方式该函数, 在汇编代码中也不能以 call 指令调用该函数
有定义: 编译器为内联函数单独生成指令, 可以被其他函数调用
局部函数: 该函数没有产生外部符号, 只能被内部函数所调用, 而不能被外部函数所调用
全局函数: 该函数产生了外部符号, 既可以被内部函数所调用, 也可以被外部函数所调用
从上表中, 我们发现 c89 标准是不支持内联函数的, 而 gnu89 支持的内联函数却与其他标准支持的内联函数产生的效果却不同
调用内联函数与访问内联函数地址
我们操作某个函数通常是调用函数, 但我们有时候会通过函数指针来调用函数函数指针中保存的其实是函数的地址如果编译器没有给内联函数单独生成指令, 那么就无从谈起函数的地址了, 也就不能通过函数指针来调用函数
如果我们在调用着函数中访问了内联函数的地址, 那么这个时候又会对这个内联函数产生了另外的影响用 inline 和 extern inline 来修饰函数产生的内联函数与上表中列出的效果一致但用 static inline 来修饰函数产生的内联函数却是有定义并且为局部函数
内联函数的应用
某个函数要么被同一个编译单元 (同一个编译单元可以理解为同一个 C 源文件) 调用, 要么被其他编译单元 (其他编译单元可以理解为不同的 C 源文件) 调用下面是应用内联函数的一点建议(不考虑 gnu89 标准):
如果我们仅希望某个内联函数只内嵌到本编译单元的某个函数, 那么我们可以用 inline 来修饰函数这个内联函数即可以定义在本编译单元的源文件中, 也可以定义在头文件中然后被包含在本编译单元的源文件中但需要注意的是, 调用者函数不能访问内联函数的地址这样没有单独为内联函数生成的指令, 所有调用内联函数的地方均直接嵌入了其指令
如果我们希望不论是本编译单元还是其他编译单元, 调用内联函数的地方均直接将其指令嵌入到调用者的代码中, 那么我们可以在头文件中定义内联函数并且用 inline 来修饰它
如果我们希望在本编译单元中采用内联方式, 而在其他编译单元中采用普通调用的方式, 那么我们可以用 extern inline 来修饰函数并且在本编译单元的源文件中定义该内联函数
如果我们希望在本编译单元中采用内联的方式, 并且本编译单元中某个地方需要访问内联函数地址, 那么就需要用 static inline 来修饰函数
如果我们希望在本编译单元中采用内联的方式, 并且本编译单元或其他编译单元中某个地方需要访问内联函数地址, 那么就需要用 extern inline 来修饰函数
前三点不考虑访问内联函数地址的情况, 后两点是考虑了访问内联函数地址的情况具体应该如何定义内联函数, 需要考虑具体的需求情况
来源: http://lib.csdn.net/article/embeddeddevelopment/36885