【问题标题】:'static' value appears to reset after function call [duplicate]“静态”值似乎在函数调用后重置[重复]
【发布时间】:2019-12-01 04:23:06
【问题描述】:

我找到了很多关于静态的文章(MSDNMSDN 2Stack Overflow 等等),但我仍然不明白为什么这段代码会返回 -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

这究竟是如何工作的?

【问题讨论】:

  • @pappbence96 这正是他所期望的,但没有得到。
  • 仅供参考,这与静态无关。
  • 已经调试过了,当方法结束时,值 = -7,但下一步它神奇地更改为“-1”。这就是看不懂的问题
  • value -= foo()value = current value of value (0) - foo() which returns 1 相同,与value = 0 - 1 相同。 foo() 确实减少了value,但只有在之后您在调用foo() 的计算中引用了它。糟糕的解释让人难以理解,但简而言之就是这样。
  • i += i++; 这样的语句本质上是含糊不清的。一些语言有法律术语,可以很好地定义这些语句,而其他语言(臭名昭著的 C 系列)只是说:“操作数评估的顺序是未定义的。” 不管你的语言属于哪个类别,以这种方式模棱两可的代码都不应该编写,因为它很难理解。

标签: c#


【解决方案1】:

声明

value -= foo(); // short for value = value - foo();

等价于

var temp = value; // 0
var fooResult = foo(); // 1
value = temp - fooResult; // -1

这就是你收到-1的原因

【讨论】:

  • @Bahrom:它似乎像你所说的那样有效,但为什么呢?
  • @EliahuAaron:因为 C# 规范是这么说的。为什么 C# 规范会这样说?因为 (a) 具有确定性的评估顺序和 (b) a -= b 表现得像 a = a - b (同时评估 a 仅一次)都被广泛认为是好主意。你会以不同的方式指定它吗?如果是,如何以及为什么?
【解决方案2】:

这个问题与静态无关;这是关于减法的工作原理。

value -= foo();可以扩展为value = value - foo()

编译器会分四步解释:

  1. value 的值加载到堆栈中。
  2. 调用方法foo并将结果放入堆栈。
  3. 对堆栈中的这两个值进行减法运算。
  4. 将结果设置回value字段。

所以value 字段的原始值已经加载。无论你在方法foo中更改value,都不会影响减法的结果。

如果将顺序更改为value = - foo() + value,则在调用foo 后会加载value 字段的值。结果是-8;这就是你应该得到的。

感谢以利亚胡的评论。

【讨论】:

  • 如果您在最后一句中将value = foo() - value 更改为value = -foo() + value;,您将得到“预期”结果-8
  • @EliahuAaron ...可能。操作数is unspecified/unsequenced 的求值顺序,因此允许编译器以任意顺序或实际上并行求值这两个参数。但是你的评论有用地强调了-= 是一个比最初看起来更复杂的东西。
  • @NormanGray 你提醒我从规范中搜索定义,链接是c++标准的,我找到one for c#,它是从左到右的。
  • @AlbertoSantini 添加部分是。你不会在数学中得到类似的东西,因为在一个不可变的世界中,问题不存在。它来自可变性 - 根据您执行valuefoo() 的顺序,您会得到不同的结果。最后的加法 a + b 是可交换的,但你如何得到 ab 不是。
  • @AlbertoSantini 因为side effects,它不是。 A + B 表示首先评估A(可能会导致副作用),保留结果,然后评估B(副作用),最后添加结果。
【解决方案3】:
value -= foo(); // value = value - foo();

foo() 将返回 1

value 最初是 0,所以:0 = 0 - 1

现在value-1

所以问题在于返回 1

【讨论】:

  • 这个答案忽略了调用foo改变value的事实。
  • @Theraot:这个答案是正确的; foo 更改 value 的事实无关紧要。不管foo 改变什么值,事实是C# 中的减法是从左到右进行的。
  • @EricLippert 确实如此,但是,它仍然不好解释。
  • @Theraot 我只解释了核心概念,由你决定。
【解决方案4】:

只看生成的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: - 将静态字段的值压入堆栈。 s:[值(0)]
  • IL_0006: - 将7 推入堆栈。 s:[7, value(0)]
  • IL_0007: - 从 value1 (0) 中减去 value2 (7),返回一个新值 (-7)。
  • IL_0008: - 将静态字段的值替换为 val (value = -7)
  • IL_000d: - 将1 推入堆栈。 s:[1, 7, value(-7)]
  • IL_000e: - 从栈中弹出一个值到局部变量 0。(lv = 1)
  • IL_0011: - 将局部变量 0 加载到堆栈上。 s:[lv(1), 7, value(-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: - 将value 推入堆栈(即0
  • IL_0006: - 调用foo(将返回1
  • IL_000b: - 从value1(0) (value(0) - value(1) = -1) 中减去值:value2(1)

所以结果是-1

【讨论】:

    【解决方案5】:

    您可以使用菜单 DebugWindowsDisassembly 并检查后台发生的情况:

    我评论了最有趣的部分。

        //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
    

    【讨论】:

      【解决方案6】:

      我认为这与它如何在汇编级别减去value 有关,这会导致程序中出现一些不一致。我不知道它是否与静态有关。但从我的直觉来看,情况是这样的:

      让我们关注value -= foo()

      1. 旧的value 被保存(推入堆栈)
      2. foo() 函数返回 1
      3. 现在,value-7,因为 foo() 操作
      4. 问题出在这里:旧的value(这是之前保存的0)减去1,结果分配给当前的value

      【讨论】:

      • 这不仅仅是一个意外——C# 规范明确定义了在这种情况下的执行顺序——编译器不允许生成会输出除-1 以外的任何内容的代码,这与 C 中不同。
      猜你喜欢
      • 1970-01-01
      • 2020-12-04
      • 1970-01-01
      • 1970-01-01
      • 2016-04-10
      • 1970-01-01
      • 2013-06-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多