Roy*_*mir 10 .net c# compiler-construction il
我写了一个简单的程序来研究IL的工作原理:
void Main()
{
int a=5;
int b=6;
if (a<b) Console.Write("333");
Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我正在努力了解实施的效率:
在#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
Run Code Online (Sandbox Code Playgroud)
我的代码示例只有5行代码.那50,000,000行代码呢?IL会发出大量额外的代码
问题2
查看代码地址:

ps我在+释放模式下使用Optimize标志
pax*_*blo 10
我可以很容易地回答第二个问题.说明书是可变长度的.例如,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.
| 归档时间: |
|
| 查看次数: |
1033 次 |
| 最近记录: |