Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
[StructLayout(LayoutKind.Explicit)]
struct Magic
{
[FieldOffset(0)]
public object Obj;
[FieldOffset(0)]
public string Str;
}
static string ItIsString(object obj)
{
Magic m = new Magic();
m.Obj = obj;
return m.Str;
}
static void Main(string[] args)
{
Console.WriteLine(ItIsString(10).Length); // 10
Console.WriteLine(ItIsString((long)'p' << 32 | 1)[0]); // p
}
Для значимыхReturnType MethodName(ref Type this, …arguments…)
Сделано это для поддержки изменяемости структур, т.е. чтобы мы могли модифицировать this.
struct Foo
{
public int Bar = 0;
}
void FooBar1(Foo foo, int bar)
{
// Работаем с копией
foo.Bar = bar;
}
void FooBar2(ref Foo foo, int bar)
{
// Работаем с самим переданным объектом
foo.Bar = bar;
}
void Main()
{
var foo = new Foo();
Console.WriteLine(foo.Bar); // Выведет 0
// Передали копию foo
FooBar1(foo, 1);
Console.WriteLine(foo.Bar); // Выведет 0
// Передали адрес foo
FooBar2(ref foo, 2);
Console.WriteLine(foo.Bar); // Выведет 2
}Можно ли привести пример кода на c#, который бы использовал эту особенность?
namespace ConsoleApplication1
{
struct MyStruct
{
public int x;
public int GetX() { return x; }
}
class MyClass
{
public int x;
public int GetX() { return x; }
}
class Program
{
static void Main(string[] args)
{
var s = new MyStruct();
s.GetX();
var c = new MyClass();
c.GetX();
}
}
}GetX() идентичный для структуры и класса (различие только в том, что в классе смещение поля +4, в структуре +0):
namespace ConsoleApplication1
{
struct MyStruct
{
public int x;
[MethodImpl(MethodImplOptions.NoInlining)]
public int GetX()
{
return x;
}
}
class MyClass
{
public int x;
[MethodImpl(MethodImplOptions.NoInlining)]
public int GetX()
{
return x;
}
}
class Program
{
unsafe static void Main(string[] args)
{
var s = new MyStruct();
s.GetX(); // breakpoint
var c = new MyClass();
c.GetX(); // breakpoint
}
}
}
var s = new MyStruct();
00000000 push ebp
00000001 mov ebp,esp
00000003 push eax
00000004 xor eax,eax
00000006 mov dword ptr [ebp-4],eax
00000009 xor edx,edx
0000000b mov dword ptr [ebp-4],edx
s.GetX(); // breakpoint
0000000e lea ecx,[ebp-4]
00000011 call dword ptr ds:[00193828h]
var c = new MyClass();
00000017 mov ecx,1938A0h
0000001c call FFF820B0
00000021 mov dword ptr [eax+4],7Bh
c.GetX(); // breakpoint
00000028 mov ecx,eax
0000002a call dword ptr ds:[00193894h]
00000030 mov esp,ebp
}
00000032 pop ebp
00000033 ret
0000000e lea ecx,[ebp-4] ??? а это есть не что иное как загрузка адреса MyStruct s var point = new System.Drawing.Point();
point.Offset(100, 500);И, да, инструкция callvirt не проверяет на “правильность” объекта.
We can use a similar dispatch sequence to call non-virtual methods as well. However, for non-virtual methods,
there is no need to use the method table for method dispatch: the code address of the invoked method (or at least
its pre-JIT stub) is known when the JIT compiles the method dispatch. For example, if the stack location EBP-64
contains the address of an Employeeobject, as before, then the following instruction sequence will call the
TakeVacationmethod with the parameter 5:
mov edx, 5 ;parameter passing through register – custom calling convention mov ecx, dword ptr [ebp-64] ;still required because ECX contains ‘this’ by convention call dword ptr [0x004a1260]
It is still required to load the object’s address into the ECXregister – all instance methods expect to receive
in ECXthe implicit thisparameter. However, there’s no longer any need to dereference the method table pointer
and obtain the address from the method table. The JIT compiler still needs to be able to update the call site after
performing the call; this is obtained by performing an indirect call through a memory location (0x004a1260in
this example) that initially points to the pre-JIT stub and is updated by the JIT compiler as soon as the method is
compiled.
Unfortunately, the method dispatch sequence above suffers from a significant problem. It allows method
calls on null object references to be dispatched successfully and possibly remain undetected until the instance
method attempts to access an instance field or a virtual method, which would cause an access violation. In fact,
this is the behavior for C++ instance method calls – the following code would execute unharmed in most C++
environments, but would certainly make C# developers shift uneasily in their chairs:
class Employee { public: void Work() { } //empty non-virtual method }; Employee* pEmployee = NULL; pEmployee->Work(); //runs to completion
If you inspect the actual sequence used by the JIT compiler to invoke non-virtual instance methods, it would
contain an additional instruction:
mov edx, 5 ;parameter passing through register – custom calling convention mov ecx, dword ptr [ebp-64] ;still required because ECX contains ‘this’ by convention cmp ecx, dword ptr [ecx] call dword ptr [0x004a1260]
Recall that the CMPinstruction subtracts its second operand from the first and sets CPU flags according to the
result of the operation. The code above does not use the comparison result stored in the CPU flags, so how would
the CMPinstruction help prevent calling a method using a null object reference? Well, the CMPinstruction attempts
to access the memory address in the ECXregister, which contains the object reference. If the object reference is
null, this memory access would fail with an access violation, because accessing the address 0 is always illegal in
Windows processes. This access violation is converted by the CLR to a NullReferenceExceptionwhich is thrown
at the invocation point; a much better choice than emitting a null check inside the method after it has already
been called. Furthermore, the CMPinstruction occupies only two bytes in memory, and has the advantage of
being able to check for invalid addresses other than null.
Age of JIT compiling. Part I. Genesis