'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:

foo=1, value=-7

But one step later, value is -1:

value = -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:

  1. Load the value of value onto the stack.
  2. Call the method foo and put the result onto the stack.
  3. Do subtraction with these two values on the stack.
  4. 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: - Push 7 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: - Push 1 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: - pushes value onto stack (which is 0)
  • IL_0006: - calls foo (which will return 1)
  • IL_000b: - subtract values: value2(1) from value1(0) (value(0) - value(1) = -1).

So the result is -1.


You can use menu DebugWindowsDisassembly 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