我有这段代码会发出一些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
指令?
答案 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。