'static' value appears to reset after function call [duplicate]
I found a lot of articles about statics (MSDN, MSDN 2, Stack Overflow, and lot lot more), but I still can't understand why this code returns -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();
}
}
Here's what the debugger shows after foo()
has run, but before the result is subtracted from value
:
But one step later, value
is -1
:
I would expect -8
because of the static field which is stored in memory once.
When I changed it to
var x = foo();
value -= x;
it shows -8
How does this work exactly?
This problem is not about static; it's about how the subtraction works.
value -= foo();
can be expanded to value = value - foo()
The compiler will explain it into four steps:
- Load the value of
value
onto the stack. - Call the method
foo
and put the result onto the stack. - Do subtraction with these two values on the stack.
- Set the result back to
value
field.
So the original value of value
field is already loaded. Whatever you change value
in the method foo
, the result of the subtraction won't be affected.
If you change the order to value = - foo() + value
, then the value of value
field will be loaded after foo
is called. The result is -8
; that's what you are expected to get.
Thanks for Eliahu's comment.
The statement
value -= foo(); // short for value = value - foo();
is equivalent to
var temp = value; // 0
var fooResult = foo(); // 1
value = temp - fooResult; // -1
That's why you are getting -1
Just look at the generated 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:
- Push the value of the static field on the stack. s:[value(0)] -
IL_0006:
- Push7
onto the stack. s:[7, value(0)] -
IL_0007:
- Subtracts value2 (7
) from value1 (0
), returning a new value (-7). -
IL_0008:
- Replaces the value of the static field with val (value = -7). -
IL_000d:
- Push1
onto the stack. s:[1, 7, value(-7)] -
IL_000e:
- Pop a value from stack into local variable 0. (lv = 1) -
IL_0011:
- Load local variable 0 onto stack. s:[lv(1), 7, value(-7)] -
IL_0012:
- Return (lv(1))
And the Main
method:
.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:
- pushesvalue
onto stack (which is0
) -
IL_0006:
- callsfoo
(which will return1
) -
IL_000b:
- subtract values:value2(1)
fromvalue1(0)
(value(0) - value(1) = -1
).
So the result is -1
.
You can use menu Debug → Windows → Disassembly and check what goes on in the background:
I commented the most interesting parts.
//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
So it is true that when writing value -= foo()
, it generates code something like this:
value = 0; // In the beginning STEP_A
//... main
var temp = value; //STEP_B
temp -= foo(); // STEP_C
value = temp; // STEP_D