"статическое" значение сбрасывается после вызова функции
Я нашел много статей о статике (MSDN, MSDN 2, Qaru и многое другое), но я до сих пор не могу понять, почему этот код возвращает -1
:
class Program
{
static int value = 0;
static int foo()
{
value = value - 7;
return 1;
}
static void Main(string[] args)
{
value -= foo();
Console.WriteLine(value);
Console.ReadKey();
}
}
Вот что показывает отладчик после запуска foo()
, но до того, как результат вычитается из value
:
Но на один шаг позже value
становится -1
:
Я ожидаю -8
из-за статического поля, которое хранится в памяти один раз.
Когда я изменил его на
var x = foo();
value -= x;
это показывает -8
Как это работает точно?
Ответы
Ответ 1
Эта проблема не о статических; это о том, как работает вычитание.
value -= foo();
может быть расширен до value = value - foo()
Компилятор объяснит это в четыре этапа:
- Загрузите значение
value
в стек. - Вызовите метод
foo
и поместите результат в стек. - Делайте вычитание с этими двумя значениями в стеке.
- Установите результат обратно в поле
value
.
Таким образом, исходное значение поля value
уже загружено. Независимо от того, что вы измените value
в методе foo
, результат вычитания не будет затронут.
Если вы измените порядок на value = - foo() + value
, то поле значения value
будет загружено после foo
. Результат -8
; то, что вы ожидаете получить.
Спасибо за комментарий Элиаху.
Ответ 2
Заявление
value -= foo(); // short for value = value - foo();
эквивалентно
var temp = value; // 0
var fooResult = foo(); // 1
value = temp - fooResult; // -1
Вот почему вы получаете -1
Ответ 3
Просто посмотрите на сгенерированный CIL:
.method private hidebysig static int32 foo() cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] int32 V_0)
IL_0000: nop
IL_0001: ldsfld int32 Program::'value'
IL_0006: ldc.i4.7
IL_0007: sub
IL_0008: stsfld int32 Program::'value'
IL_000d: ldc.i4.1
IL_000e: stloc.0
IL_000f: br.s IL_0011
IL_0011: ldloc.0
IL_0012: ret
} // end of method Program::foo
-
IL_0001:
- IL_0001:
значение статического поля в стек. с: [значение (0)] -
IL_0006:
- Вставьте 7
в стек. s: [7, значение (0)] -
IL_0007:
- вычитает значение 2 (7
) из значения 1 (0
), возвращая новое значение (-7). -
IL_0008:
- Заменяет значение статического поля на val (value = -7). -
IL_000d:
- Вставить 1
в стек. s: [1, 7, значение (-7)] -
IL_000e:
- IL_000e:
значение из стека в локальную переменную 0. (lv = 1) -
IL_0011:
- загрузить локальную переменную 0 в стек. s: [lv (1), 7, значение (-7)] -
IL_0012:
- возврат (lv (1))
И Main
метод:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 29 (0x1d)
.maxstack 8
IL_0000: nop
IL_0001: ldsfld int32 Program::'value'
IL_0006: call int32 Program::foo()
IL_000b: sub
IL_000c: stsfld int32 Program::'value'
IL_0011: ldsfld int32 Program::'value'
IL_0016: call void [mscorlib]System.Console::WriteLine(int32)
IL_001b: nop
IL_001c: ret
} // end of method Program::Main
-
IL_0001:
- IL_0001:
value
в стек (0
) -
IL_0006:
- вызывает foo
(который вернет 1
) -
IL_000b:
- вычесть значения: value2(1)
из значения1 value1(0)
(value(0) - value(1) = -1
).
Таким образом, результат -1
.
Ответ 4
Вы можете использовать меню Debug → Windows → Disassembly и проверить, что происходит в фоновом режиме:
Я прокомментировал самые интересные части.
//static int value = 0;
05750449 mov ebp,esp
0575044B push edi
0575044C push esi
0575044D push ebx
0575044E sub esp,2Ch
05750451 xor edx,edx
05750453 mov dword ptr [ebp-10h],edx
05750456 mov dword ptr [ebp-1Ch],edx
05750459 cmp dword ptr ds:[15E42D8h],0
05750460 je 05750467
05750462 call 55884370
05750467 xor edx,edx
05750469 mov dword ptr ds:[15E440Ch],edx // STEP_A place 0 in ds register
somewhere
0575046F nop
05750470 lea esp,[ebp-0Ch]
05750473 pop ebx
05750474 pop esi
05750475 pop edi
05750476 pop ebp
05750477 ret
//value -= foo();
057504AB mov eax,dword ptr ds:[015E440Ch] // STEP_B places (temp) to eax. eax now contains 0
057504B0 mov dword ptr [ebp-40h],eax
057504B3 call 05750038
057504B8 mov dword ptr [ebp-44h],eax
057504BB mov eax,dword ptr [ebp-40h]
057504BE sub eax,dword ptr [ebp-44h] //STEP_C substract the return(-1) of call from the temp eax
057504C1 mov dword ptr ds:[015E440Ch],eax // STEP_D moves eax (-1) value to our ds register to some memory location
//Console.WriteLine(value);
015E04C6 mov ecx,dword ptr ds:[015E440Ch] // Self explanatory; move our ds(-1) to ecx, and then print it out to the screen.
015E04CC call 54CE8CBC
Так что это правда, что при записи value -= foo()
, он генерирует код примерно так:
value = 0; // In the beginning STEP_A
//... main
var temp = value; //STEP_B
temp -= foo(); // STEP_C
value = temp; // STEP_D
Ответ 5
Я думаю, что это как-то связано с тем, как вычитается value
на уровне сборки, и это вызывает некоторую несогласованность в программе. Я не знаю, имеет ли это какое-то отношение к статике или нет. Но из моей интуиции вот что происходит:
Позвольте сосредоточиться на value -= foo()
- Старое
value
сохраняется (помещается в стек) - Функция
foo()
возвращает 1
- Теперь
value
равно -7
из-за операции foo()
- ЗДЕСЬ проблема: старое
value
(которое было сохранено ранее, то есть 0
) вычитается из 1
а результат присваивается текущему value
.
Ответ 6
value -= foo(); // value = value - foo();
foo()
вернет 1
.
value
изначально равно 0
, поэтому: 0 = 0 - 1
.
Теперь value
имеет -1
.
Так что проблема с возвратом 1
.
Ответ 7
Кажется, использование метода приращения неверно. Если вы хотите увеличить, вы не должны вызывать ту же самую переменную для увеличения. Это статическая переменная, но при использовании +=
есть кеш, я имею в виду, что значение вычисляется перед вычислением), потому что это одна строка для потока на компьютере, поэтому GET слева направо, а затем SET вправо для оставил на той же линии.
демонстрация
public static int value
{
get
{
Console.WriteLine($"get value: {_value}");
return _value;
}
set
{
var old = _value;
_value = value;
Console.WriteLine($"set value> old: {old}, new: {value}");
}
}
static int _value = 0;
static int foo()
{
Console.WriteLine($"######## foo() is called ######## ");
value = value - 7;
Console.WriteLine($"new value is now '-7'");
Console.WriteLine($"######## foo() is returning '1' ######## ");
return 1;
}
static void Main(string[] args)
{
Console.WriteLine($"STARTING");
//value -= foo(); // TOTALLY EQUALS WITH BELOW
value = value - foo();
Console.WriteLine($"FINISHED");
Console.WriteLine(value);
Console.ReadKey();
}
Выход
STARTING
get value:0
######## foo() is called ########
get value:0
set value> old: 0, new: -7
new value is now '-7'
######## foo() is returning '1' ########
set value> old: -7, new: -1
FINISHED
get value: -1
-1
Как видите, вторая строка получает value
переменной value
и затем вызывает foo();
метод, поэтому он перезаписывает -7
в -1
, потому что компьютер знает, прежде чем начинать вычислять значение 0
. Даже если вам нужно увеличить, компьютер все еще использует формат value = NEWvalue
сложения, например value = NEWvalue
, но где-то value = NEWvalue
текущее значение.
Примечание: если вы работаете с асинхронным приращением для одной и той же переменной, вы увидите, что некоторые значения пропали.