如果我有这个变量:
string name;
会在内存中分配一个位置吗?或者只有在我将其初始化为特定值时才会获得分配的内存?即,
string name = "Jack";
例如,请考虑以下代码:
for (int i = 0; i < 20; i++) {
Run();
}
private void Run() {
int age = 20;
}
内存中的age
值会发生什么变化?是否会在每次执行Run方法时从内存中删除它?或者在代码执行后它会留在内存中并在使用它的程序关闭后删除吗?
答案 0 :(得分:1)
字符串名称;
如果这是您唯一的声明,编译器可能会优化并删除它。如果没有优化,这将是对null的引用。
string name =“Jack”;
这将在堆中创建一个内存分配来存储字符串本身。它还将在堆栈中生成一个指针,其中包含已分配堆内存的地址。在退出方法和堆栈弹出时,堆中分配的内存将不再具有引用,并且可以标记为垃圾回收。
您的20次迭代将生成20个堆栈分配,每个堆栈分配在堆栈中的值为20,而堆中没有生成任何内容。退出方法后,将弹出堆栈并丢失数据。
答案 1 :(得分:1)
对于任何.NET值类型变量,如int
,bool
,double
等;一旦你声明它就会发生内存分配,当你为它赋值时,这个值就会在内存中更新。
另一方面,对于包括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变量将在每个方法调用的堆栈中分配,并应在每个方法调用退出时释放。