MAIL代码中的可变范围/重用

时间:2011-07-26 12:34:25

标签: c# scope

在一次同行评审期间调试一些C#代码时,我注意到一种奇怪的行为,起初似乎是某种范围违规,但回想起来看起来可能是编译器试图通过重用引用来节省内存。代码是:

for(int i = 0; i < 10; i++)
{
    // Yadda yadda, something happens here
}

// At this point, i is out of scope and is not
// accessible. This is verified by intellisense
// and by attempting to look at the variable 
// during debug
string whatever = "";

// At this point if I put a break on the following
// for line, I can look at the variable I before
// it is initialized and see that it already holds
// the value of 10. If a different variable name
// is used, I get a value of 0 (not initialized).
for(int i = 0; i < 10; i++)
{
    // Inside the loop, i has been re-initialized
    // so it performs its function as expected
}

编译器是否只是重用现有的引用?在需要更密切地管理变量/引用的C / C ++中,这将是我期望的行为。使用C#,我的印象是每次在循环范围内声明变量时它会分区出一个新的独立内存部分,但显然情况并非如此。这是一个内存保存功能,可能是C / C ++行为的延续,或者这种情况被忽略了,因为编译器强迫你重新初始化?

<击> 修改
我在进行其他检查时注意到的一些事情是,这种行为不会在类中的方法之间展示。它确实出现在多个using语句中,但只有在类型和名称相同时才会显示。

经过进一步调查,我开始相信这不是关于MISL代码,而是关于IDE将这些引用保留在自己的内存中。我没有看到任何迹象表明这种行为实际上存在于代码级别,所以现在我倾向于认为这只是IDE的一个怪癖。

编辑2:
看起来@Vijay Gill的答案反驳了IDE的怪癖。

2 个答案:

答案 0 :(得分:3)

必须是这样的,编译器重用相同的变量: (这已经是你的例子中最可能的,但只是为了表明使用了真正相同的地址......)

证明:(两个变量共享相同的内存地址)

public unsafe void test()
{
    for (int i = 0; i < 10; i++)
    {
        // Yadda yadda, something happens here 
        int* ptr = &i; 
        IntPtr addr = (IntPtr)ptr; 
        if (i == 9)
        {
            Console.WriteLine(addr.ToString("x")); 
            MessageBox.Show(addr.ToString("x"));
        }
    }

    for (int i = 0; i < 10; i++)
    {
        int* ptr = &i;
        IntPtr addr = (IntPtr)ptr;
        if (i == 9)
        {
            Console.WriteLine(addr.ToString("x"));
            MessageBox.Show(addr.ToString("x"));
        }
    } 
}

看到反编译的版本会很有趣。

答案 1 :(得分:3)

这完全取决于编译器以及用于编译的配置。在下面的文本转储中,您可以看到在发布模式下,两个int变量被声明在dubug模式中的地方,只有一个。

为什么它这样做完全超出我的范围(暂时,当我回家时我会调查更多)

编辑:查看此答案末尾的更多调查结果

    private static void f1()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Loop 1");
        }

        Console.WriteLine("Interval");

        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Loop 2");
        }
    }

发布模式:(注意局部变量i&amp; V_1)

.method private hidebysig static void  f1() cil managed
{
  // Code size       57 (0x39)
  .maxstack  2
  .locals init ([0] int32 i,
           [1] int32 V_1)
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  br.s       IL_0012
  IL_0004:  ldstr      "Loop 1"
  IL_0009:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000e:  ldloc.0
  IL_000f:  ldc.i4.1
  IL_0010:  add
  IL_0011:  stloc.0
  IL_0012:  ldloc.0
  IL_0013:  ldc.i4.s   10
  IL_0015:  blt.s      IL_0004
  IL_0017:  ldstr      "Interval"
  IL_001c:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0021:  ldc.i4.0
  IL_0022:  stloc.1
  IL_0023:  br.s       IL_0033
  IL_0025:  ldstr      "Loop 2"
  IL_002a:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_002f:  ldloc.1
  IL_0030:  ldc.i4.1
  IL_0031:  add
  IL_0032:  stloc.1
  IL_0033:  ldloc.1
  IL_0034:  ldc.i4.s   10
  IL_0036:  blt.s      IL_0025
  IL_0038:  ret
} // end of method Program::f1

调试模式:(注意局部变量i)

.method private hidebysig static void  f1() cil managed
{
  // Code size       73 (0x49)
  .maxstack  2
  .locals init ([0] int32 i,
           [1] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  br.s       IL_0016
  IL_0005:  nop
  IL_0006:  ldstr      "Loop 1"
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0010:  nop
  IL_0011:  nop
  IL_0012:  ldloc.0
  IL_0013:  ldc.i4.1
  IL_0014:  add
  IL_0015:  stloc.0
  IL_0016:  ldloc.0
  IL_0017:  ldc.i4.s   10
  IL_0019:  clt
  IL_001b:  stloc.1
  IL_001c:  ldloc.1
  IL_001d:  brtrue.s   IL_0005
  IL_001f:  ldstr      "Interval"
  IL_0024:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0029:  nop
  IL_002a:  ldc.i4.0
  IL_002b:  stloc.0
  IL_002c:  br.s       IL_003f
  IL_002e:  nop
  IL_002f:  ldstr      "Loop 2"
  IL_0034:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0039:  nop
  IL_003a:  nop
  IL_003b:  ldloc.0
  IL_003c:  ldc.i4.1
  IL_003d:  add
  IL_003e:  stloc.0
  IL_003f:  ldloc.0
  IL_0040:  ldc.i4.s   10
  IL_0042:  clt
  IL_0044:  stloc.1
  IL_0045:  ldloc.1
  IL_0046:  brtrue.s   IL_002e
  IL_0048:  ret
} // end of method Program::f1

生成的汇编代码如下。这仅适用于在发布模式下编译的IL。现在即使是机器语言(在这里反汇编),我看到创建了两个局部变量。我无法找到任何答案。只有MS人才能告诉我们。但是,当我们编写与堆栈使用相关的递归方法时,这种行为非常重要。

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  sub         esp,0Ch 
00000006  mov         dword ptr [ebp-4],ecx 
00000009  cmp         dword ptr ds:[04471B50h],0 
00000010  je          00000017 
00000012  call        763A4647 

-- initialisation of local variables
-- this is why we get all ints set to zero initially (will see similar behavioir for other types too)
00000017  xor         edx,edx 
00000019  mov         dword ptr [ebp-8],edx 
0000001c  xor         edx,edx 
0000001e  mov         dword ptr [ebp-0Ch],edx

00000021  xor         edx,edx -- zero out register edx which will be saved to memory where i (first one) is located
00000023  mov         dword ptr [ebp-8],edx -- initialise variable i (first one) with 0
00000026  nop 
00000027  jmp         00000037 -- jump to the loop condition

00000029  mov         ecx,dword ptr ds:[01B32088h] 
0000002f  call        76A84E7C -- calls method to print the message "Loop 1"

00000034  inc         dword ptr [ebp-8] -- increment i (first one) by 1
00000037  cmp         dword ptr [ebp-8],0Ah  -- compare with 10
0000003b  jl          00000029 -- if still less, go to address 00000029

0000003d  mov         ecx,dword ptr ds:[01B3208Ch]
00000043  call        76A84E7C -- prints the message "Half way there"

00000048  xor         edx,edx  -- zero out register edx which will be saved to memory where i (second one) is located
0000004a  mov         dword ptr [ebp-0Ch],edx -- initialise i (second one) with 0
0000004d  nop 
0000004e  jmp         0000005E -- jump to the loop condition

00000050  mov         ecx,dword ptr ds:[01B32090h] 
00000056  call        76A84E7C -- calls method to print the message "Loop 1"

0000005b  inc         dword ptr [ebp-0Ch]  -- increment i (second one) by 1
0000005e  cmp         dword ptr [ebp-0Ch],0Ah -- compare with 10
00000062  jl          00000050  -- if still less, go to address 00000050


00000064  nop 
00000065  mov         esp,ebp 
00000067  pop         ebp 
00000068  ret