内联函数(inline)机制与陷阱

内容提要:

 

1.内联相对于宏的优点

 

2.为什么“inline”只是程序员对编译器的建议(而非强制命令)

 

3.内联失败的陷阱

 

CC++中函数调用需要少量开销。有时候这少量开销积少成多,对程序性能造成影响。有时候函数本身很简单,函数调用的开销比执行函数内容本身的开销还大。C程序员一定知道可以采用宏(Macro)机制来改善上述情况。但是宏基本上是在预编译阶段做文本替换,因此它有以下缺陷:

 

1.它无法进行类型检查;

 

2.传入有副作用(side effect)的表达式作为参数可能引起微妙的程序臭虫;

 

3.无法单步调试。

 

4.代码膨胀。

 

内联机制被引入C++作为对宏(Macro)机制的改进和补充(不是取代)。内联函数的参数传递机制与普通函数相同。但是编译器会在每处调用内联函数的地方将内联函数的内容展开。这样既避免了函数调用的开销又没有宏机制的前三个缺陷。

 

但是程序代码中的关键字“inline”只是对编译器的建议:被“inline”修饰的函数不一定被内联(但是无“inline”修饰的函数一定不是)。

 

许多书上都会提到这是因为编译器比绝大多数程序员都更清楚函数调用的开销有多大,所以如果编译器认为调用某函数的开销相对该函数本身的开销而言微不足道或者不足以为之承担代码膨胀的后果则没必要内联该函数。这当然有一定道理,但是按照CC++一脉相承的赋予程序员充分自由与决定权的风格来看,理由还不够充分。我猜想最主要的原因是为了避免编译器陷入无穷递归。如果内联函数之间存在递归调用则可能导致编译器展开内联函数时陷入无穷递归。有时候函数的递归调用十分隐蔽,程序员并不容易发现,所以简单起见,将内联与否的决定权交给编译器。

 

另一种不被内联的情况是使用函数指针来调用内联函数。

 

 

对于C++中内联机制的一个常见误解是:关键字“inline“只是对编译器的建议,如果编译器发现指定的函数不适合内联就不会内联;所以即使内联使用的不恰当也不会有任何副作用。这句话只对了一半,内联使用不恰当是会有副作用的:会带来代码膨胀,还有可能引入难以发现的程序臭虫。

 

 

根据规范,当编译器认为希望被内联的函数不适合内联的时候,编译器可以不内联该函数。但是不内联该函数不代表该函数就是一个普通函数了,从编译器的实际实现上来讲,内联失败的函数与普通函数是有区别的:

 

普通的函数在编译时被单独编译一个对象,包含在相应的目标文件中。目标文件链接时,函数调用被链接到该对象上。

 

若一个函数被声明成内联函数,编译器即使遇到该函数的声明也不会为该函数编译出一个对象,因为内联函数是在用到的地方展开的。可是若在调用该内联函数的地方发现该内联函数的不适合展开时怎么办?一种选择是在调用该内联函数的目标文件中为该内联函数编译一个对象。这么做的直接后果是:若在多个文件调用了内联失败的函数,其中每个文件对应的目标文件中都会包含一份该内联函数的目标代码。

 

如果编译器真的选择了上面的做法对待内联失败的函数,那么最好的情况是:没吃到羊肉,反惹了一身骚。即内联的好处没享受到,缺点却承担了:目标代码的体积膨胀得与成功内联的目标代码一样,但目标代码的效率确和没内联一样。

 

更糟的是由于存在多份函数目标代码带来一些程序臭虫。最明显的例子是:内联失败的函数内的静态变量实际上就不在只有一份,而是有若干份。这显然是个错误,但是如果不了解内幕就很难找到原因。

 

另:不知道C++ template instantiate机制是否与内联机制相似,但似乎没有类似问题,这是为什么?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值