C 箴言:理解inline的介入和排除
2008-02-23 05:40:47来源:互联网 阅读 ()
inline 函数——多么棒的主意啊!他们看起来像函数,他们产生的效果也像函数,他们在各方面都比宏好得太多太多,而您却能够在调用他们时不招致函数调用的成本。您更有什么更多的需要呢?
实际上您得到的可能比您想的更多,因为避免函数调用的成本只是故事的一部分。在典型情况下,编译器的优化是为了一段连续的没有函数调用的代码设计的,所以当您 inline 化一个函数,您可能就使得编译器能够对函数体实行上下文相关的特别优化。大多数编译器都不会对 "outlined" 函数调用实行这样的优化。
然而,在编程中,就像在生活中,没有免费午餐,而 inline 函数也不例外。一个 inline 函数背后的思想是用函数本体代替每一处对这个函数的调用,而且不必拿着统计表中的 Ph.D. 就能够看出这样可能会增加您的目标代码的大小。在有限内存的机器上,过分热衷于 inline 化会使得程式对于可用空间来说过于庞大。即使使用了虚拟内存,inline 引起的代码膨胀也会导致附加的分页调度,减少指令缓存命中率,连同随之而来的性能损失。
在另一方面,假如一个 inline 函数本体很短,为函数本体生成的代码可能比为一个函数调用生成的代码还要小。假如是这种情况,inline 化这个函数能够实际上导致更小的目标代码和更高的指令缓存命中率! 记住,inline 是向编译器发出的一个请求,而不是个命令。这个请求能够以显式的或隐式的方式提出。隐式的方法就是在一个类定义的内部定义一个函数:
class Person {
public:
...
int age() const { return theAge; } // an implicit inline request: age is
... // defined in a class definition
private:
int theAge;
};
这样的函数通常是成员函数,但是我们知道友元函数也能被定义在类的内部,假如他们在那里,他们也被隐式地声明为 inline。
显式的声明一个 inline 函数的方法是在他的声明之前加上 inline 关键字。例如,以下就是标准 max 模板(来自 <algorithm>)经常用到的的实现方法:
template<typename T> // an explicit inline
inline const T& std::max(const T& a, const T& b) // request: std::max is
{ return a < b ? b : a; } // preceded by "inline"
max 是个模板的事实引出一个观察结论:inline 函数和模板一般都是定义在头文档中的。这就使得一些程式员得出结论断定函数模板必须是 inline。这个结论是非法的而且有潜在的危害,所以他值得我们考察一下。 inline 函数一般必须在头文档内,因为大多数构建环境在编译期间进行 inline 化。为了用被调用函数的函数本体替换一个函数调用,编译器必须知道函数看起来像什么样子。(有一些构建环境能够在连接期间进行 inline 化,更有少数几个——比如,基于 .NET Common Language Infrastructure (CLI) 的控制环境——居然能在运行时 inline 化。然而,这些环境都是例外,并非规则。inline 化在大多数 C 程式中是个编译时行为。)
模板一般在头文档内,因为编译器需要知道一个模板看起来像什么以便用到他时对他进行实例化。(同样,也不是全部如此。一些构建环境能够在连接期间进行模板实例化。然而,编译期实例化更为普遍。) 模板实例化和 inline 化无关。假如您写了一个模板,而且您认为任何从这个模板实例化出来的函数都应该是 inline 的,那么就声明这个模板为 inline,这就是上面的 std::max 的实现被做的事情。但是假如您为没有理由要 inline 化的函数写了一个模板,就要避免声明这个模板为 inline(无论显式的还是隐式的)。inline 化是有成本的,而且您不希望在毫无预见的情况下遭遇他们。我们已说到 inline 化是如何引起代码膨胀的,但是,更有其他的成本,过一会儿我们再讨论。
在做这件事之前,我们先来完成对这个结论的考察:inline 是个编译器可能忽略的请求。大多数编译器拒绝他们认为太复杂的 inline 函数(例如,那些包含循环或递归的),而且,除了最细碎的以外的全部虚拟函数的调用都不会被 inline 化。不应该对这后一个结论感到惊讶。虚拟意味着“等待,直到运行时才能断定哪一个函数被调用”,而 inline 意味着“执行之前,用被调用函数取代调用的地方”。假如编译器不知道哪一个函数将被调用,您很难责备他们拒绝 inline 化这个函数本体。
任何这些加在一起,得出:一个被指定的 inline 函数是否能真的被 inline 化,取决于您所使用的构建环境——主要是编译器。幸运的是,大多数编译器都有一个诊断层次,在他们不能 inline 化一个您提出的函数时,会导致一个警告。
有时候,即使当编译器完全心甘情愿地 inline 化一个函数,他们还是会为这个 inline 函数生成函数本体。例如,假如您的程式要持有一个 inline 函数的地址,编译器必须为他生成一个 outlined 函数本体。他们怎么能生成一个指向根本不存在的函数的指针呢?再加上,编译器一般不会对通过函数指针的调用进行 inline 化,这就意味着,对一个 inline 函数的调用可能被也可能不被 inline 化,依赖于这个调用是如何做成的:
inline void f() {...} // assume compilers are willing to inline calls to f
void (*pf)() = f; // pf points to f
...
f(); // this call will be inlined, because it’s a "normal" call
pf(); // this call probably won’t be, because it’s through
// a function pointer
甚至在您从来没有使用函数指针的时候,未 inline 化的 inline 函数的幽灵也会时不时地拜访您,因为程式员并不必然是函数指针的唯一需求者。有时候编译器会生成构造函数和析构函数的 out-of-line 拷贝,以便他们能得到指向这些函数的指针,在对数组中的对象进行构造和析构时使用。
[1] [2] 下一页
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇: C 箴言:绝不在构造或析构期调用虚函数
下一篇: C 箴言:将强制转型减到最少
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash