Переменная область / повторное использование в коде MSIL - PullRequest
3 голосов
/ 26 июля 2011

Во время отладки некоторого кода 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 ]

3 голосов
/ 26 июля 2011

Это полностью зависит от компилятора и конфигурации, которую вы используете для компиляции. В следующем текстовом дампе вы можете видеть, что в режиме Release две переменные 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 & 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, скомпилированного только в режиме Release. Теперь даже на машинном языке (разобранном здесь) я вижу, что созданы две локальные переменные. Я не мог найти никакого ответа на это. Только парни 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 
3 голосов
/ 26 июля 2011

должно быть так, что компилятор использует одну и ту же переменную: (это было наиболее вероятно на вашем примере, но только для того, чтобы показать, что действительно один и тот же адрес используется ...)

доказательство: (обе переменные имеют один и тот же адрес памяти)

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"));
        }
    } 
}

было бы интересно увидеть декомпилированную версию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...