为什么IL.Emit方法会添加其他nop指令?

时间:2018-09-16 00:28:35

标签: c# cil reflection.emit ilgenerator

我有这段代码会发出一些IL指令,该指令会在string.IndexOf对象上调用null

MethodBuilder methodBuilder = typeBuilder.DefineMethod(
                                             "Foo",
                                             MethodAttributes.Public,
                                             typeof(void), Array.Empty<Type>());
var methodInfo = typeof(string).GetMethod("IndexOf", new[] {typeof(char)});
ILGenerator ilGenerator = methodBuilder.GetILGenerator();

ilGenerator.Emit(OpCodes.Ldnull);
ilGenerator.Emit(OpCodes.Ldc_I4_S, 120);
ilGenerator.Emit(OpCodes.Call, methodInfo);
ilGenerator.Emit(OpCodes.Ret);

这是生成的IL代码:

.method public instance int32  Foo() cil managed
{
  // Code size       12 (0xc)
  .maxstack  2
  IL_0000:  ldnull
  IL_0001:  ldc.i4.s   120
  IL_0003:  nop
  IL_0004:  nop
  IL_0005:  nop
  IL_0006:  call       instance int32 [mscorlib]System.String::IndexOf(char)
  IL_000b:  ret
} // end of method MyDynamicType::Foo

如您所见,在nop指令之前有三个call指令。

首先,我想到了Debug / Release构建,但这不是编译器生成的代码,我发出的是原始IL代码,希望能照原样查看。

所以我的问题是,当我没有发出任何指令时,为什么会有三个nop指令?

2 个答案:

答案 0 :(得分:13)

ILGenerator不是非常高级,如果您使用Emit(OpCode, Int32)重载,则它将整个int32放入指令流中,无论操作码是否为Ldc_I4(实际上需要4个字节的立即数)或Ldc_I4_S(不是)。

因此请确保使用正确的重载:

ilGenerator.Emit(OpCodes.Ldc_I4_S, (byte)120);

文档中的lemmas for the opcodes指定要使用的重载Emit


reference source中,Emit带有一个int参数:

public virtual void Emit(OpCode opcode, int arg) 
{
    // Puts opcode onto the stream of instructions followed by arg
    EnsureCapacity(7);
    InternalEmit(opcode);
    PutInteger4(arg);
}

PutInteger4在其中建立IL的字节数组中写入四个字节。

Emit的文档说,多余的字节将是Nop指令,但这仅是当它们实际上为零时。如果传递的值“更错误”(高字节不为零),则从无效的操作码到微妙破坏结果的操作,后果可能更糟。

答案 1 :(得分:5)

IlGenerator.Emit的documentation提到了这一点:

  

备注如果opcode参数需要一个参数,则调用者必须   确保参数长度与声明的长度匹配   参数。否则,结果将不可预测。例如,如果   Emit指令需要一个2字节的操作数,并且调用方提供   一个4字节的操作数,运行时将向其发送另外两个字节   指令流。这些额外的字节将是Nop指令。

     

指令值在操作码中定义。

文档中提到了您的说明

Ldc_I4_S
int8 的值作为 int32 的缩写形式推入评估堆栈。

似乎另外三个点来自int8而不是int32。

相关问题