inline函数

引子程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// file.cc
inline void foo(void)
{
/*函数体*/
}

// file.h
void foo(void);

// main.cc
int main()
{
foo();
}

上面的编码会出现什么问题? 链接错误,显示foo未定义(undefined reference to `foo()’).

编译器对inline函数的处理

使用inline函数,意图是在函数调用处直接替换成函数体,省去函数调用,提高函数的执行效率.但编译器在处理的时候,真正的处理流程是什么样的呢?

我们知道,c/c++程序编译分为几个阶段,预处理->编译+汇编->链接.inline的处理阶段发生在编译阶段.也就是说,在编译阶段完成后,目标模块的inline函数要么被替换了,要么没有被替换,但是链接时可以找到inline函数的定义.也就是说,在编译阶段之前,目标模块需要看到inline函数的实现.

拿引子程序来说,编译main.o目标模块时,编译器”看到”了foo的声明,此时它并不知道foo是要inline的.编译file.o目标模块时,编译器看到foo的实现,并且声明为inline,由于foo在file.o没有被使用到,所以,inline函数不被保留.链接的时候,当然也就找不到foo的定义实体了.针对此例子,我们可以在file.c定义一处函数,使用了一次foo(),编译就可以通过.

1
2
3
4
5
//file.cc
int foo2()
{
foo();
}

正确的做法是,inline函数的实现(定义)要跟随其函数声明放在.h文件中.编译器在编译阶段就可以看见函数实现,从而进行替换.(这一点有点向函数模板,函数模板也是要放在声明文件中,不能单独放在源文件中)

inline和宏的区别

  • 1.宏define在预处理阶段完成;inline在编译阶段完成;
  • 2.类型安全检查,inline是函数,需要做类型检查;
  • 3.替换方式:宏是字符串拷贝替换,会出现边际效应;inline是嵌入代码,在编译过程中不单独产生代码。
  • 4.宏不可调试,inline函数可以调试。这里的“调试”之意是指在程序的debug版本是没有内联的,编译器会生成像普通函数一样含有调试信息的可执行代码。在release版本,才实现真正的内联。
  • 5.inline函数可以操作私有数据成员。

内联函数的使用

inline函数的编程风格

关键字inline必须与函数定义放在一起才能使函数真正内联,仅把inlilne放在函数声明的前面不起任何作用.

1
2
3
4
5
6
7
8
9
10
11
12
13
//Foo不能内联
inline void Foo(int x, int y);
void Foo(int x, int y)
{
...
}

//Foo内联
void Foo(int x, int y);
inline Foo(int x, int y)
{
...
}

inline是一种”用于实现的关键字”,而不是一种”用于声明的关键字”.

慎用内联

  • 如果函数体内的代码过长,使用内联将导致可执行代码膨胀过大
  • 如果函数体内的代码出现循环或者其他复杂的控制结构,那么执行函数体内代码的时间比函数调用的开销大得多,因此内联的意义不大.

不要轻易让构造函数和析构函数成为内联函数

inline仅仅是对编译器的一种请求,编译器可以无视

inline常跟static一起

当多个源文件包含同一个inline函数,但是编译器又无视inline请求,如此一来,在每个目标模块中都有该函数的实现实例,造成重定义错误。把inline函数同时声明为static函数(限制其作用域为源文件内部),可以解决问题。