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
	calladd
	movl%eax, %esi
	movl$.LC0, %edi
	movl$0, %eax
	callprintf
	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与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来修饰函数。
前三点不考虑访问内联函数地址的情况,后两点是考虑了访问内联函数地址的情况。具体应该如何定义内联函数,需要考虑具体的需求情况。
	
	Linux下gcc与g++的差别:http://www.linuxdiyf.com/linux/13654.html
fedora22无法联网的情况下rpm安装gcc5.1:http://www.linuxdiyf.com/linux/12804.html
Linux编译安装GCC 5.1.0:http://www.linuxdiyf.com/linux/11923.html
在Mac OS X 10.10.3下使用源码包编译安装GCC5.1:http://www.linuxdiyf.com/linux/11906.html
Linux源码安装GCC编译器:http://www.linuxdiyf.com/linux/7348.html

