繁体   English   中英

在.net中的IL和堆栈实现?

[英]IL & stack implementation in .net?

我写了一个简单的程序来研究IL的工作原理:

void Main()
{

 int a=5;
 int b=6;
 if (a<b) Console.Write("333");
 Console.ReadLine();
}

IL:

IL_0000:  ldc.i4.5    
IL_0001:  stloc.0     
IL_0002:  ldc.i4.6    
IL_0003:  stloc.1     
IL_0004:  ldloc.0     
IL_0005:  ldloc.1     
IL_0006:  bge.s       IL_0012
IL_0008:  ldstr       "333"
IL_000D:  call        System.Console.Write
IL_0012:  call        System.Console.ReadLine

我正在努力了解实施的效率:

  • 在#1行(IL代码),它将值5推送到堆栈上(4个字节,即int32)

  • 在第2行(IL代码),它从堆栈POP到局部变量。

接下来的两行也是如此。

然后,它将那些局部变量加载到堆栈上, 然后它评估bge.s

问题#1

他为什么要将局部变量加载到堆栈中? 已经在堆栈中。 但是为了把它们放在局部变量中,他将它们加以限制。 这不是浪费吗?

我的意思是,为什么代码不能像:

IL_0000:  ldc.i4.5
IL_0001:  ldc.i4.6    
IL_0002:  bge.s       IL_0004
IL_0003:  ldstr       "333"
IL_0004:  call        System.Console.Write
IL_0005:  call        System.Console.ReadLine

我的代码示例只有5行代码。 那50,000,000行代码呢? IL会发出大量额外的代码

问题2

查看代码地址:

在此输入图像描述

  • IL_0009地址在哪里? 它应该是顺序的吗?

ps我在+释放模式下使用Optimize标志

我可以很容易地回答第二个问题。 说明书是可变长度的。 例如, ldstr "333"ldstr的操作码(在地址8 )和随后的表示字符串的数据(对用户字符串表中的字符串的引用)组成。

与之后的call语句类似 - 您需要call操作码本身以及要调用的函数的信息。

将诸如4或6之类的小值推送到堆栈上的指令没有额外数据的原因是因为这些值被编码到操作码本身中。

请参阅此处获取说明和编码。

关于第一个问题,您可能希望查看C#开发人员之一Eric Lippert撰写的博客文章

/ optimize标志不会改变我们的大量发射和生成逻辑。 我们尝试始终生成简单,可验证的代码,然后依靠抖动在生成真实机器代码时进行大量优化。

关于此级别的IL效率没有必要进行推理。

JIT将完全消除堆栈,将所有堆栈操作转换为中间三地址代码(并进一步转换为SSA)。 由于IL 永远不会被解释,因此堆栈操作不应该是高效和优化的。

例如,请参阅开源Mono实现。

他为什么要将局部变量加载到堆栈中? 这些值已经在堆栈中。 但他为了将它们置于局部变量而加入了它们。 这不是浪费吗?

浪费了什么? 您必须记住IL(通常)不会按原样执行,它由JIT编译器再次编译,JIT编译器执行大多数优化。 使用“中间语言”的一个要点是可以在一个地方实现优化:JIT编译器和每种语言(C#,VB.NET,F#,...)不必再重复实现它们。 Eric Lippert在他的文章为什么IL?

IL_0009地址在哪里? 它不应该是顺序的吗?

我们来看看ldstr指令的规范(来自ECMA-335 ):

III.4.16 ldstr - 加载文字字符串

格式:72 <T> [...]

ldstr指令将表示存储在元数据中的文字的新字符串对象推送为字符串(字符串文字)。

对上面的元数据的引用和<T>意味着指令的字节72后面跟着一个元数据标记,它指向包含字符串的表。 这样的象征有多大? 从同一文件的第III.1.9节:

许多CIL指令后面跟着“元数据标记”。 这是一个4字节的值,用于指定元数据表中的行[...]

因此,在您的情况下,指令的字节72位于地址0008处,并且令牌(在这种情况下为0x70000001,其中0x70字节表示用户字符串表)位于地址0009至000C。

对所有关于“额外代码”的讨论给出最终答案。

C#编译器读取int a=5; 并将其翻译为:

ldc.i4.5
stloc.0

然后它转到下一行并读取int b=6; 这被翻译成:

ldc.i4.6
stloc.1

然后它用if语句读取下一行,依此类推。

当从C#编译为IL时,它逐行读取并将该行转换为IL,而不是在查看其他行时将该行转换为IL。

要在此阶段优化IL并删除“额外代码”(您称之为“额外代码”),C#编译器必须检查所有IL代码,构建它的树表示,删除所有不需要的节点,然后再将其写为IL 。 这不是C#编译器应该做的事情,因为当从IL到机器语言时,这将由JIT编译器完成。

因此,您认为额外的代码不是额外的代码,它是C#编译器从C#代码读取的语句的一部分,并且在JIT编译器将代码编译为本机可执行文件时将被删除。

这是对C#代码如何翻译的高级解释,因为我认为你没有在编译器构造中使用任何类或类似的东西。 如果您想了解更多信息,请访问互联网上的书籍和页面。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM