В промежуточном языке на основе стека, таком как байт-код CIL или Java, зачем нужны локальные переменные? Можно было бы просто использовать только стек. Это может быть не так просто для созданного вручную IL, но компилятор, безусловно, может это сделать. Но мой компилятор С# этого не делает.
И стек, и локальные переменные являются частными для метода и выходят из области видимости, когда метод возвращается. Так что это не могло иметь ничего общего с побочными эффектами, видимыми снаружи метода (из другого потока).
Компилятор JIT устранит загрузку и сохранение как в слоты стека, так и в локальные переменные при генерации машинного кода, если я прав, поэтому компилятор JIT также не видит необходимости в локальных переменных.
С другой стороны, компилятор C# генерирует загрузки и сохранения для локальных переменных даже при компиляции с включенными оптимизациями. Почему?
Возьмем, к примеру, следующий надуманный пример кода:
static int X()
{
int a = 3;
int b = 5;
int c = a + b;
int d;
if (c > 5)
d = 13;
else
d = 14;
c += d;
return c;
}
При компиляции на C# с оптимизацией получается:
ldc.i4.3 # Load constant int 3
stloc.0 # Store in local var 0
ldc.i4.5 # Load constant int 5
stloc.1 # Store in local var 1
ldloc.0 # Load from local var 0
ldloc.1 # Load from local var 1
add # Add
stloc.2 # Store in local var 2
ldloc.2 # Load from local var 2
ldc.i4.5 # Load constant int 5
ble.s label1 # If less than, goto label1
ldc.i4.s 13 # Load constant int 13
stloc.3 # Store in local var 3
br.s label2 # Goto label2
label1:
ldc.i4.s 14 # Load constant int 14
stloc.3 # Store in local var 3
label2:
ldloc.2 # Load from local var 2
ldloc.3 # Load from local var 3
add # Add
stloc.2 # Store in local var 2
ldloc.2 # Load from local var 2
ret # Return the value
Обратите внимание на загрузку и сохранение четырех локальных переменных. Я мог бы написать точно такие же операции (не обращая внимания на очевидную оптимизацию распространения констант) без использования каких-либо локальных переменных.
ldc.i4.3 # Load constant int 3
ldc.i4.5 # Load constant int 5
add # Add
dup # Duplicate top stack element
ldc.i4.5 # Load constant int 5
ble.s label1 # If less than, goto label1
ldc.i4.s 13 # Load constant int 13
br.s label2 # Goto label2
label1:
ldc.i4.s 14 # Load constant int 14
label2:
add # Add
ret # Return the value
Мне кажется, это правильно, и намного короче и эффективнее. Итак, почему промежуточные языки на основе стека имеют локальные переменные? И почему оптимизирующий компилятор так широко их использует?