c#:内存中的变量会发生什么?

时间:2017-10-26 23:45:32

标签: c# variables allocation

如果我有这个变量:

string name;

会在内存中分配一个位置吗?或者只有在我将其初始化为特定值时才会获得分配的内存?即,

string name = "Jack";

例如,请考虑以下代码:

for (int i = 0; i < 20; i++) {
    Run();
}

private void Run() {
    int age = 20;
}

内存中的age值会发生什么变化?是否会在每次执行Run方法时从内存中删除它?或者在代码执行后它会留在内存中并在使用它的程序关闭后删除吗?

4 个答案:

答案 0 :(得分:1)

  

字符串名称;

如果这是您唯一的声明,编译器可能会优化并删除它。如果没有优化,这将是对null的引用。

  

string name =“Jack”;

这将在堆中创建一个内存分配来存储字符串本身。它还将在堆栈中生成一个指针,其中包含已分配堆内存的地址。在退出方法和堆栈弹出时,堆中分配的内存将不再具有引用,并且可以标记为垃圾回收。

您的20次迭代将生成20个堆栈分配,每个堆栈分配在堆栈中的值为20,而堆中没有生成任何内容。退出方法后,将弹出堆栈并丢失数据。

答案 1 :(得分:1)

对于任何.NET值类型变量,如intbooldouble等;一旦你声明它就会发生内存分配,当你为它赋值时,这个值就会在内存中更新。

另一方面,对于包括string的引用类型,只在内存中分配一个地址,该地址创建对存储当前值的实际内存位置的引用(类似于C / C ++中的指针)。

因此,在您的示例中,age一旦运行int age,就会在内存中创建20,当age = 20时,它的值将设置为Run()被执行。

每次执行make-method-map方法时,都会为其分配一个新的内存位置。

答案 2 :(得分:0)

如果您有此代码:

void Main()
{
    string name;
}

然后,在使用编译器优化进行编译(在LINQPad中)时,您将获得以下IL:

IL_0000:  ret

没有优化:

IL_0000:  nop         
IL_0001:  ret         

没有为此声明分配内存 - 只是作为未优化代码的IL中的占位符的NOP操作。

当你的程序是这样的时候:

void Main()
{
    string name = "Jack";
}

然后你编译优化代码是:

IL_0000:  ret

编译器只是忽略未使用的变量。

未经优化的代码生成:

IL_0000:  nop         
IL_0001:  ldstr       "Jack"
IL_0006:  stloc.0     // name
IL_0007:  ret         

显然,未经优化的代码更具说明性,因此除非我明确另有说明,否则我将仅显示未经优化的代码。

现在让我们让代码做一些更有趣的事情。

void Main()
{
    string name = "Jack";
    Console.WriteLine(name);
}

这会产生:

IL_0000:  nop         
IL_0001:  ldstr       "Jack"
IL_0006:  stloc.0     // name
IL_0007:  ldloc.0     // name
IL_0008:  call        System.Console.WriteLine
IL_000D:  nop         
IL_000E:  ret         

有趣的是,如果我们将此代码更改为:

void Main()
{
    int answer = 42;
    Console.WriteLine(answer);
}

我们得到了这个:

IL_0000:  nop         
IL_0001:  ldc.i4.s    2A 
IL_0003:  stloc.0     // answer
IL_0004:  ldloc.0     // answer
IL_0005:  call        System.Console.WriteLine
IL_000A:  nop         
IL_000B:  ret         

代码与string示例几乎相同。

ldstr调用获取对字符串文字的引用(存储在大对象堆上的字符串池中(不是作为小对象堆的普通堆)并将其推送到评估堆栈

ldc.i4.s正在将数字42的引用推送到评估堆栈。

然后,在这两种情况下,stloc.0都会将评估堆栈顶部的值存储到方法的第0个本地内存位置。

然后,在两种情况下,ldloc.0都会从第零个本地内存位置加载值并将其放在评估堆栈上。

你可以想象一下编译器在优化这段代码时可能会做些什么。

最后制作了System.Console.WriteLine

现在让我们更详细地看一下那个讨厌的string代码。

我说它存储在实习池中。我们来检查一下。

拿这段代码:

void Main()
{
    string name = "Jack";
    Console.WriteLine(String.IsInterned(name));
}

它产生:

IL_0000:  nop         
IL_0001:  ldstr       "Jack"
IL_0006:  stloc.0     // name
IL_0007:  ldloc.0     // name
IL_0008:  call        System.String.IsInterned
IL_000D:  call        System.Console.WriteLine
IL_0012:  nop         
IL_0013:  ret

它将Jack输出到控制台。只有在System.String.IsInterned返回一个实习字符串时才能这样做。

使用此程序显示相反的结果:

void Main()
{
    string name = String.Join("", new [] { "Ja", "ck" });
    Console.WriteLine(String.IsInterned(name));
}

它将null推送到控制台 - 意味着字符串name未被实现,因此在这种情况下name存储在堆上(小对象堆)。

让我们看看你的第二段代码:

void Main()
{
    for (int i = 0; i < 20; i++)
    {
        Run();
    }
}

private void Run()
{
    int age = 20;
}

如果我们查看优化的IL,那么Run方法如下所示:

Run:
IL_0000:  ret        

未经优化的IL是:

Run:
IL_0000:  nop         
IL_0001:  ldc.i4.s    14 
IL_0003:  stloc.0     // age
IL_0004:  ret 

并且,就像我之前使用int的示例一样,它将文字值20(或十六进制为14)加载到评估堆栈中,然后立即将其存储在本地内存为方法,然后返回。因此,对于局部变量age重复使用相同的内存20次。

答案 3 :(得分:-1)

在您的第一个示例(未初始化的变量)中,它不会分配任何内存,因为它不会生成任何MSIL。它将与没有代码完全相同。 如果初始化它,将在当前方法的堆栈中分配内存。

第二种情况,age变量将在每个方法调用的堆栈中分配,并应在每个方法调用退出时释放。