Почему X86 для следующего метода С# CallViaStruct
включает инструкцию cmp
?
struct Struct {
public void NoOp() { }
}
struct StructDisptach {
Struct m_struct;
[MethodImpl(MethodImplOptions.NoInlining)]
public void CallViaStruct() {
m_struct.NoOp();
//push ebp
//mov ebp,esp
//cmp byte ptr [ecx],al
//pop ebp
//ret
}
}
Вот более полная программа, которая может быть скомпилирована с различными (выпускными) декомпиляциями в виде комментариев. Я ожидал, что X86 для CallViaStruct
в типах ClassDispatch
и StructDispatch
будет одинаковым, но версия в StructDispatch
(извлеченная выше) включает в себя инструкцию cmp
, а другая - нет.
Кажется, инструкция cmp
- это идиома, используемая для обеспечения того, что переменная не равна null; разыменование регистра со значением 0 запускает av
, который превращается в NullReferenceException
. Однако в StructDisptach.CallViaStruct
я не могу представить способ для ecx
быть пустым, если он указывает на структуру.
UPDATE: ответ, который я хочу принять, будет включать код, который вызывает NRE, который должен быть сброшен StructDisptach.CallViaStruct
, если он cmp
разыменовал команду с нулевым ecx
регистром. Обратите внимание, что это легко сделать с любым из методов CallViaClass
, установив m_class = null
и сделать невозможным с ClassDisptach.CallViaStruct
, поскольку нет инструкции cmp
.
using System.Runtime.CompilerServices;
namespace NativeImageTest {
struct Struct {
public void NoOp() { }
}
class Class {
public void NoOp() { }
}
class ClassDisptach {
Class m_class;
Struct m_struct;
internal ClassDisptach(Class cls) {
m_class = cls;
m_struct = new Struct();
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void CallViaClass() {
m_class.NoOp();
//push ebp
//mov ebp,esp
//mov eax,dword ptr [ecx+4]
//cmp byte ptr [eax],al
//pop ebp
//ret
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void CallViaStruct() {
m_struct.NoOp();
//push ebp
//mov ebp,esp
//pop ebp
//ret
}
}
struct StructDisptach {
Class m_class;
Struct m_struct;
internal StructDisptach(Class cls) {
m_class = cls;
m_struct = new Struct();
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void CallViaClass() {
m_class.NoOp();
//push ebp
//mov ebp,esp
//mov eax,dword ptr [ecx]
//cmp byte ptr [eax],al
//pop ebp
//ret
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void CallViaStruct() {
m_struct.NoOp();
//push ebp
//mov ebp,esp
//cmp byte ptr [ecx],al
//pop ebp
//ret
}
}
class Program {
static void Main(string[] args) {
var classDispatch = new ClassDisptach(new Class());
classDispatch.CallViaClass();
classDispatch.CallViaStruct();
var structDispatch = new StructDisptach(new Class());
structDispatch.CallViaClass();
structDispatch.CallViaStruct();
}
}
}
UPDATE: позволяет использовать callvirt
для не виртуальной функции, которая имеет побочный эффект от нулевой проверки этого указателя. Хотя это имеет место для CallViaClass
callsite (именно поэтому мы видим нулевую проверку там) StructDispatch.CallViaStruct
использует инструкцию call
.
.method public hidebysig instance void CallViaClass() cil managed noinlining
{
// Code size 12 (0xc)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class NativeImageTest.Class NativeImageTest.StructDisptach::m_class
IL_0006: callvirt instance void NativeImageTest.Class::NoOp()
IL_000b: ret
} // end of method StructDisptach::CallViaClass
.method public hidebysig instance void CallViaStruct() cil managed noinlining
{
// Code size 12 (0xc)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldflda valuetype NativeImageTest.Struct NativeImageTest.StructDisptach::m_struct
IL_0006: call instance void NativeImageTest.Struct::NoOp()
IL_000b: ret
} // end of method StructDisptach::CallViaStruct
UPDATE: было высказано предположение, что cmp
может быть ловушкой для случая, когда a null
этот указатель не был захвачен на сайте вызова. Если бы это было так, я бы ожидал, что cmp
произойдет один раз в верхней части метода. Однако он появляется один раз для каждого вызова NoOp
:
struct StructDisptach {
Struct m_struct;
[MethodImpl(MethodImplOptions.NoInlining)]
public void CallViaStruct() {
m_struct.NoOp();
m_struct.NoOp();
//push ebp
//mov ebp,esp
//cmp byte ptr [ecx],al
//cmp byte ptr [ecx],al
//pop ebp
//ret
}
}