简体   繁体   English

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

[英]c#: What happens in a variable in memory?

If I have this variable:如果我有这个变量:

string name;

will it be allocated a location in the memory?它会在内存中分配一个位置吗? Or it will only get allocated memory when I initialize it to a specific value?或者它只会在我将其初始化为特定值时才获得分配的内存? Ie, IE,

string name = "Jack";

For example, consider the following code:例如,考虑以下代码:

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

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

What will happen to the age value in the memory?内存中的age值会发生什么变化? Will it be removed from memory in each execution of the Run method?它会在每次执行 Run 方法时从内存中删除吗? Or will it stay in the memory after the code is executed and removed after the program that uses it close?还是会在代码执行后留在内存中,并在使用它的程序关闭后移除?

string name;字符串名称;

If this is your only statement, the compiler will probably optimize and remove it.如果这是您唯一的语句,编译器可能会优化并删除它。 Without optimizations, this will be a reference to null.如果没有优化,这将是对 null 的引用。

string name = "Jack";字符串名称 = "杰克";

This will create a memory allocation in the heap to store the string itself.这将在堆中创建一个内存分配来存储字符串本身。 It will also generate a pointer in your stack with the address of the allocated heap memory.它还将在您的堆栈中生成一个指针,其中包含分配的堆内存的地址。 Upon exiting the method and stack popped, the allocated memory in the heap will no longer have a reference and can be flagged for garbage collection.退出方法并弹出堆栈后,堆中分配的内存将不再具有引用,并且可以标记为垃圾回收。

Your 20 iterations will generate 20 stack allocations, each of which would have the value 20 in the stack with nothing generated in the heap.您的 20 次迭代将生成 20 个堆栈分配,每个堆栈分配的堆栈中的值为 20,堆中没有生成任何内容。 Upon exiting the method, the stack would be popped and the data lost.退出该方法时,堆栈将被弹出并丢失数据。

For any .NET value type variable like int , bool , double , etc.;对于任何 .NET 值类型变量,如intbooldouble等; the memory allocation happens as soon as you declare it and when you assign value to it, the value is just updated in memory.内存分配会在您声明它后立即发生,并且当您为其分配值时,该值只会在内存中更新。

For reference types including string , on the other hand, only an address is assigned in memory which creates a reference to the actual memory location where the current value is stored (similar to pointers in C/C++).另一方面,对于包括string在内的引用类型,只在内存中分配一个地址,这会创建对存储当前值的实际内存位置的引用(类似于 C/C++ 中的指针)。

So, in your example, age will be created in memory as soon as the int age is run and then it's value will be set to 20 when the age = 20 gets executed.因此,在您的示例中,只要int age运行, age就会在内存中创建,然后当age = 20被执行时,它的值将设置为20

It'll be assigned a new memory location each time the Run() method is executed.每次执行Run()方法时,它都会被分配一个新的内存位置。

If you have this code:如果你有这个代码:

void Main()
{
    string name;
}

Then, when compiled (in LINQPad) with compiler optimisation you get the following IL:然后,当使用编译器优化编译(在 LINQPad 中)时,您会得到以下 IL:

IL_0000:  ret

And without optimisation:并且没有优化:

IL_0000:  nop         
IL_0001:  ret

There is no memory allocated for this declaration - just a NOP operation as a placeholder in the IL for the unoptimised code.没有为此声明分配内存 - 只是一个 NOP 操作作为未优化代码的 IL 中的占位符。

When your program is this:当你的程序是这样的:

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

Then you compiler optimised code is:然后你编译器优化的代码是:

IL_0000:  ret

The compiler simply ignores the unused variable.编译器只是忽略未使用的变量。

The unoptimised code generates this:未优化的代码生成以下内容:

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

Obviously the unoptimised code is more explanatory, so I'll only show unoptimsed code from now on unless I explicitly say otherwise.显然,未优化的代码更具解释性,所以从现在开始我将只显示未优化的代码,除非我明确说明。

Now let's make the code do something more interesting.现在让我们让代码做一些更有趣的事情。

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

This produces:这产生:

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

What's interesting is if we change this code to this:有趣的是,如果我们将此代码更改为:

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

We get this:我们得到这个:

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

The code is virtually the same as the string example.代码实际上与string示例相同。

The ldstr call is getting a reference to a string literal (which is store in the String Pool on the Large Object Heap (not the normal heap which is the Small Object Heap) and pushing it on the evaluation stack. ldstr调用正在获取对字符串文字的引用(它存储在大对象堆上的字符串池中(不是普通堆,即小对象堆)并将其推送到计算堆栈上。

The ldc.i4.s is pushing a reference to the number 42 on to the evaluation stack. ldc.i4.s正在将数字42的引用推送到评估堆栈。

Then, in both cases, stloc.0 is storing the value on top of the evaluation stack into the zeroth local memory location for the method.然后,在这两种情况下, stloc.0都将计算堆栈顶部的值存储到该方法的第零个本地内存位置。

Then, in both cases again, ldloc.0 is loading the value from the zeroth local memory location and putting it on the evaluation stack.然后,在这两种情况下, ldloc.0都会从第零个本地内存位置加载值并将其放在计算堆栈上。

You can probably imagine what the compiler might do if it were optimising this code.您可能可以想象如果编译器优化此代码,它会做什么。

Finally the System.Console.WriteLine is made.最后制作了System.Console.WriteLine

Now let's look at that pesky string code in more detail.现在让我们更详细地看看那个讨厌的string代码。

I said that it's stored in the Intern Pool.我说它存储在实习生池中。 Let's check that.让我们检查一下。

Take this code:拿这个代码:

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

It produces:它产生:

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

And it outputs Jack to the console.并将Jack输出到控制台。 It can only do that if System.String.IsInterned returns an interned string.只有当System.String.IsInterned返回一个 interned 字符串时,它才能做到这一点。

Take this program to show the opposite:拿这个程序来说明相反的情况:

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

It pushed out null to the console - meaning that the string name isn't interned so in this case name is stored on the heap (Small Object Heap).它将null推送到控制台 - 这意味着字符串name没有被保留,因此在这种情况下, name存储在堆(小对象堆)中。

Let's look at your second bit of code:让我们看看你的第二段代码:

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

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

If we look at optimised IL then the Run method looks like this:如果我们查看优化的 IL,那么Run方法如下所示:

Run:
IL_0000:  ret

The unoptimised IL is this:未优化的 IL 是这样的:

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

And, like my earlier example with an int , it is loading the literal value 20 (or 14 in hex) onto the evaluation stack and then immediately storing it in the local memory for the method, and then returns.而且,就像我之前使用int示例一样,它将文字值20 (或十六进制的14 )加载到计算堆栈中,然后立即将其存储在该方法的本地内存中,然后返回。 And hence it is repeating using the same memory 20 times for the local variable age .因此,它对局部变量age重复使用相同的内存 20 次。

In your first example (uninitialized variable), it will not allocate any memory since it won't generate any MSIL.在您的第一个示例(未初始化的变量)中,它不会分配任何内存,因为它不会生成任何 MSIL。 It will be the same as no code at all.这将与根本没有代码相同。 If you initialize it, memory will be allocated in the current method's stack.如果初始化它,内存将分配在当前方法的堆栈中。

Second case, the age variable will be allocated in the stack for each method call and should be released when each method call exits.第二种情况,age 变量将在每次方法调用的堆栈中分配,并应在每次方法调用退出时释放。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM