В С# есть ключевое слово ref. Используя ref, вы можете передать int методу по ссылке. Что происходит с фреймом стека при вызове метода, который принимает int по ссылке?
public void SampleMethod(ref int i) { }
В С# есть ключевое слово ref. Используя ref, вы можете передать int методу по ссылке. Что происходит с фреймом стека при вызове метода, который принимает int по ссылке?
public void SampleMethod(ref int i) { }
На низком уровне ссылочная локальная переменная int
будет помещена в стек (большую часть времени целые числа хранятся в регистрах), а указатель на стек будет передан вызываемой функции (сам указатель, скорее всего, будет передан в реестре). Рассмотрим следующий пример:
var i = 7;
Console.WriteLine(i);
inc(ref i);
Console.WriteLine(i);
Это будет JIT-et к чему-то вроде этого (целевая архитектура - x86):
17: var i = 7;
# allocate space on the stack for args and i
00482E3B sub esp,8
# initialize i to 0
00482E3E xor eax,eax
00482E40 mov dword ptr [ebp-8],eax
# args saved to stack (could be optimised out)
00482E43 mov dword ptr [ebp-4],ecx
00482E46 cmp dword ptr ds:[3ACAECh],0
00482E4D je 00482E54
00482E4F call 7399CB2D
# i = 7
00482E54 mov dword ptr [ebp-8],7
18: Console.WriteLine(i);
# load the value of i into ecx, and call cw
00482E5B mov ecx,dword ptr [ebp-8]
00482E5E call 72E729DC
19: inc(ref i);
# load the address of i into ecx, and call inc
00482E63 lea ecx,[ebp-8]
00482E66 call dword ptr ds:[4920860h]
20: Console.WriteLine(i);
# load the value of i into ecx, and call cw
00482E6C mov ecx,dword ptr [ebp-8]
00482E6F call 72E729DC
21: }
00482E74 nop
00482E75 mov esp,ebp
00482E77 pop ebp
00482E78 ret
Здесь происходит почти то же самое, получается адрес поля или элемента и указатель передается функции:
var i = new[]{7};
Console.WriteLine(i[0]);
inc(ref i[0]);
Console.WriteLine(i[0]);
Компилируется в (без скучной части):
18: Console.WriteLine(i[0]);
00C82E91 mov eax,dword ptr [ebp-8]
00C82E94 cmp dword ptr [eax+4],0
00C82E98 ja 00C82E9F
00C82E9A call 7399BDC2
00C82E9F mov ecx,dword ptr [eax+8]
00C82EA2 call 72E729DC
19: inc(ref i[0]);
# loading the reference of the array to eax
00C82EA7 mov eax,dword ptr [ebp-8]
# array boundary check is inlined
00C82EAA cmp dword ptr [eax+4],0
00C82EAE ja 00C82EB5
# this would throw an OutOfBoundsException, but skipped by ja
00C82EB0 call 7399BDC2
# load the address of the element in ecx, and call inc
00C82EB5 lea ecx,[eax+8]
00C82EB8 call dword ptr ds:[4F80860h]
Обратите внимание, что в этом случае массив не должен быть закреплен, потому что фреймворк знает, что адрес в ecx
указывает на элемент внутри массива, поэтому, если сжатие кучи происходит между lea
и call
или внутри функции inc, он может перенастройте значение ecx
напрямую.
Вы можете исследовать JIT-сборку самостоятельно, используя отладчик Visual Studio, открыв окно Разборка (Отладка /Windows/Разборка)
Адрес локальной переменной или поля. В IL для локальной переменной используется ldloca.s
.
Загружает адрес локальной переменной по определенному индексу в стек оценки
Команда stind
используется для сохранения значения обратно в переменной
Сохранить значение типа (...) в памяти по адресу
Адрес 32/64 бит, в зависимости от целевой архитектуры.
Вот простой пример в коде С#:
void Main()
{
int i = 1;
inc(ref i);
Console.WriteLine(i);
}
public void inc(ref int i) {
i++;
}
Вот сгенерированный код IL
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0 // i
IL_0003: ldarg.0
IL_0004: ldloca.s 00 // i
IL_0006: call inc
IL_000B: nop
IL_000C: ldloc.0 // i
IL_000D: call System.Console.WriteLine
IL_0012: nop
IL_0013: ret
inc:
IL_0000: nop
IL_0001: ldarg.1
IL_0002: dup
IL_0003: ldind.i4
IL_0004: ldc.i4.1
IL_0005: add
IL_0006: stind.i4
IL_0007: ret
Обратите внимание, что в этом простом случае действительно существует только одна разница ldloca.s 00 или ldloc.0. Загрузка локального или загрузочного адреса (смещения 00)
Это разница на самом простом уровне (это то, о чем вы просили в своем комментарии) - если вы загружаете значение переменной или загружаете адрес переменной. Вещь может быстро усложниться - если вызываемая вами функция не является локальной, если переменная, которую вы передаете, не является локальной и т.д. И т.д. Но на базовом уровне это разница.
Я использовал linqpad для выполнения моей быстрой диссонализации - я рекомендую его. http://www.linqpad.net/
Он будет передавать локальную переменную по ссылке вместо отправки новой копии для нее