我主要使用C#开发,但我认为这个问题也适用于其他语言。
此外,似乎这里有很多代码,但问题很简单。< / em>的
根据我的理解,内联是编译器(在C#是虚拟机的情况下)通过在调用方法的每个位置插入方法体来替换方法调用。
假设我有以下程序:
static Main()
{
int number = 7;
bool a;
a = IsEven(number);
Console.WriteLine(a);
}
......方法IsEven
的主体:
bool IsEven(int n)
{
if (n % 2 == 0) // Two conditional return paths
return true;
else
return false;
}
我可以理解在内联方法后代码的样子:
static Main()
{
int number = 7;
bool a;
if (number % 2 == 0)
a = true;
else
a = false;
Console.WriteLine(a); // Will print true if 'number' is even, otherwise false
}
一个明显简单而正确的程序。
但是,如果我稍微调整IsEven
的主体以包含绝对返回路径......
bool IsEven(int n)
{
if (n % 2 == 0)
return true;
return false; // <- Absolute return path!
}
在某些情况下,我个人更喜欢这种风格。一些折射工具甚至可能会建议我将第一个版本改为看起来像这个 - 但是当我试图想象这个方法在内联时是什么样子我感到难过。
如果我们内联方法的第二个版本:
static Main()
{
int number = 7;
bool a;
if (number % 2 == 0)
a = true;
a = false;
Console.WriteLine(a); // Will always print false!
}
要问的问题:
编译器/虚拟机如何处理内联具有绝对返回路径的方法?
这样的事情似乎不太可能真正阻止方法内联,所以我想知道如何处理这些事情。也许内联的过程并不像这样简单?也许一个版本更有可能被VM内联?
修改
分析这两种方法(以及第一种方法的手动内联)显示性能没有差异,因此我只能假设两种方法都以相同或相似的方式内联并工作(至少在我的VM上)。
此外,这些方法非常简单,几乎可以互换,但是具有绝对返回路径的复杂方法可能更难以更改为没有绝对返回路径的版本。
答案 0 :(得分:6)
可能有点难以解释JITter在内联时所做的事情 - 它不会使C#代码变为内联 - 它会(总是?)处理生成的字节(编译版本) - 以及在生成汇编代码(实际的机器代码字节)时,你所拥有的“工具”比你在C#(或IL中)所拥有的更精细。
也就是说,通过考虑break
关键字,你可以用C#术语了解它是如何工作的。
考虑可能性,即每个内联函数都不包含在while (true)
循环(或do while(false)
循环)中 - 并且每个源返回都被转换为localVar = result; break;
个语句集。然后你得到这样的东西:
static Main()
{
int number = 7;
bool a;
while (true)
{
if (number % 2 == 0)
{
a = true;
break;
}
a = false;
break;
}
Console.WriteLine(a); // Will always print the right thing! Yey!
}
类似地,在生成程序集时,您将看到生成了很多jmp
- 这些是break语句的道德等价物,但它们更灵活(将它们视为匿名的getos或其他东西)
所以你可以看到,抖动(以及任何编译为native的编译器)都有很多工具可以用来做“正确的事情”。
答案 1 :(得分:3)
return
语句表示:
finally
块中的using
块和Dispose调用将会运行。所有这些事情仍然发生在内联之后。内联后跳转将是本地的而不是交叉功能,但它仍然存在。
内联不是像C / C ++宏那样的文本替换。
内联和文本替换之间的其他不同之处是处理具有相同名称的变量。
答案 2 :(得分:2)
cpu执行的机器代码是一种非常简单的语言。它没有return语句的概念,子例程只有一个入口点和一个退出点。所以你的IsEven()方法是这样的:
bool IsEven(int n)
{
if (n % 2 == 0)
return true;
return false;
}
需要通过抖动重写为类似于此的东西(无效的C#):
void IsEvent(int n)
{
if (n % 2 == 0) {
$retval = true;
goto exit;
}
$retval = false;
exit:
} // $retval becomes the function return value
$ retval变量在这里可能看起来很假。它不是,它是x86核心上的EAX寄存器。您现在将看到此代码很容易内联,它可以直接移植到Main()的主体中。 $ retval变量可以等同于a
变量,只需简单的逻辑替换。