【C/C++_非_值班室】归来哟,故乡的云:Inline再讨论 (翻译)星星兄们,进来帮个忙。外加放分引人看~
dot99 2004-07-13 09:03:22 下面的文章翻译自GotW.
由于最近工作+学校的原因,暂时不会上网了,要等把所有事情都搞定了再说~
后面两段没翻译完,留在硬盘里面又觉得心里面痒,大家帮忙翻译一个~
用句常用的话拜托一下:“冰天雪地裸女960度回旋飞跪,泪如雨下,声嘶力竭,xxxx, ...., ****, @@@@, 拜托了~~~~”(这女的好累...)
====================
归来哟,故乡的云:Inline再讨论
作者: Herb Sutter
翻译: hardcoreX.雪糕.dot99.计算机她姑爹 (其实都是我)
快!选择一个。
什么时候内联表现出来?
A.写代码的时候
B.编译的时候
C.连接的时候
D.程序装入的时候
E.运行期
F.其他某个时候
另外:什么函数能够保证永远不会被内联?
打住!想想,然后再往下看。
你选啥呢?如果你选了A或者B,呵呵,你有伴了。这两个是常见的答案,去看看[1]的第十二款,我对C++中inline关键字的详细讨论。如果那解释就够了的话,我们就停在这里了,放编辑们一个假,然后再把剩下的栏目做了。但是现在不能,因为还有好多要讨论,很多很多。
我写这篇文章是想说明:第一个问题的最准确的答案是“全部正确或者没有一个正确。”第二个问题的答案是“没有。”为什么呢?继续往下看。
如果一已经阅读了[1]的第十二款,下面的部分就是一个复习,如果你没有思考过C答案的话,你也可以轻轻掠过。
简单的说,“内联行为(inlining)”就是将函数在调用的地方原地展开函数体。看看下面的代码:
// Example 1
//
double Square( double x ) { return x * x; }
int main() {
double d = Square(3.14159 * 2.71828 );
}
内联后,这个函数调用就好像以如下代码替代了(概念上):
int main() {
const double __temp = 3.14159 * 2.71828;
double d = __temp * __temp;
}
这个内联动作消除了调用函数的开销,即是参数压栈和代码转移的开销,因而也省去了临时引用(losing some locality of reference)。这个内联行为不等同于宏,因为一个内联函数调用一样的是一个函数调用,它的参数只被评估[译注:平时都叫“计算”,但是我觉得这里用“评估”比较好]一次。但是,一个宏则要将它评估多次,即宏#define SquareMacro(x) ((x)*(x))在调用的时候,如SquareMacro(3.14159*2.71828),要被展开为3.14159 * 2.71828 * 3.14159 * 2.71828(计算PI*e两次,而不是一次)。
顺便提一下,你注意到例1出现内联行为(inlining)没有?我们没有使用关键字inline。这是故意的。等下我们还要来讨论这个有趣的问题。
答案A:写代码的时候
当开发者们写程序的时候,念出inline的咒语。那不是真正的就内联(没有发生内联行为),只是一个想要内联试图而已,所以,我们可以将A看做是最早的决定内联动做(inlining)的一个机会。
当你试图在你的代码里面写上inline关键字的时候,你必须要记得三件重要的事情。
首先,不要那么做。过早的优化会带来麻烦,你不应该在没有证明需要加入inline才能提高效率的情况下试图加入这个关键字。翻翻[1]的第十二款和[3],里面夸张地指责和严重警告这种程序过早优化和过早决定内联的行为。
它(inline)仅仅是“请,一定要”的意思。这个在[1]的第十二款说过,inline关键字只不过是对编译器的暗示,让你巴结(sweet talk)编译器,让他给你这样做。(关于”巴结”的卑劣行径,下面讨论。)这就是全部:inline关键字并没被要求在C++程序中拥有任何语义上的效果。它并没有影响到标准语言的其他部分,用了inline关键字并没有要你改变函数的用法(例如,你仍然可以获得这个函数的地址),也不可能要标准C++编译器去检测一个函数究竟被声明为内联没有。
行为(inlining)真正表现出来,是在调用的时候。这个差别非常重要,因为这个函数能(应该经常能)在被调用的地方被内联而不是其它的地方;写下inline没有给你任何方式去表达这个事实。因为我们能做的只有在函数前面写inline关键字,我们这样做了,是给编译器含蓄的说,这个函数适合在调用的地方内联。这个预见[译注:内联一个函数]很少准确。我们经常说“内联这个函数”, 准确的说,你最好增加一下你的词汇量,应该是“内联这个函数的调用”("inlining a function call")
答案B:编译的时候
在编译的时候,编译器会像例1那样例行公事。
编译器会做什么?当我们勾对她(sweet-talk it, 哈哈哈),给她说,这个函数要内联的时候。这个要看是谁了。并不是所有女人[译注:原文是编译器]对甜言蜜语有反应,甚至在巧克力和鲜花的轰炸下。有时,你的编译器(或者其他工具)会不甩你而我行我素:
l 拒绝内联你要求内联的函数调用。
l 内联你并没有声明的函数的调用。
l 内联一些函数调用而不管声明要内联没有,或者一些地方内联,另外一些地方则不。
再注意例1,我们并没有在任何地方写下“inline”。这是故意的,我是想说明,那个样子一样会发生内联行为。确实是,不需要对你现在使用的编译器所做的一切大惊小怪。因为,你也不能再写出一个其他的程序来找茬,这是一个合理的优化,编译器能(而且经常能)为了你的利益着想。
现在的编译器能比程序员在决定那个函数调用需要内联上要出色,包括是否要展开同一个函数在某个调用的地方,而不在另外的地方。为什么?最简单的答案是,编译器比你知道得要多,它知道调用的“真实”的结构——函数调用被优化后的机器码,包括解开循环和去除无效分枝(“if-else”, “switch-case”),所有的这些工作,编译器都做了。例如,编译器也许会检测到这个被内联的函数如果放进一个循环里面,会使这个循环过大,而代码cache不能容纳,导致整体性能下降。编译器会选择在这个循环里面不内联这个函数调用,而也许会在其他地方内联它。