在一次同行评审期间调试一些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的怪癖。
答案 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