Variable lifetime

做~自己de王妃 提交于 2020-03-21 20:27:09

问题


What happends to variable when line of execution goes outside of code block? For example:

1  public void myMethod()
2  {
3     int number;
4     number = 5;
5  }

so, we declare and set variable. When it goes outside of code block (line 5) what happends to variable number?

Here is another example with creating instance of class:

7   public void myMethod()
8   {
9      Customer myClient;
10     myClient = new Customer();
11  }

When it goes outside of code block (line 11) what happends to object reference myClient?

I guess in both cases variable is allocated but when it is deallocated?


回答1:


As a variable it's a concept in the C# language. Nothing "happens" to it outside of the code block, because it is inside the code block. Nothing happens to the word word outside this sentence.

Of course, you mean what happens to the thing that variable becomes when the code is run, but it's worth remembering the distinction, because in considering that question we're shifting to levels where variables are not as they are in C#.

In both cases the code is turned into CIL, and then into machine code when it is run.

The CIL could differ quite a bit. For example, here's how the first looks when compiled in debug mode:

.method public hidebysig instance void myMethod () cil managed 
{
  .locals init ([0] int32) // Set-up space for a 32-bit value to be stored
  nop                      // Do nothing
  ldc.i4.5                 // Push the number 5 onto the stack
  stloc.0                  // Store the number 5 in the first slot of locals
  ret                      // Return
}

And here's how it looks when compiled to release:

.method public hidebysig instance void myMethod () cil managed 
{
  ret                      // Return
}

Since the value isn't used, the compiler removes that as useless cruft and just compiles a method that immediately returns.

If the compiler didn't remove such code, we might expect something like:

.method public hidebysig instance void myMethod () cil managed 
{
  ldc.i4.5                 // Push the number 5 onto the stack
  pop                      // Remove value from stack
  ret                      // Return
}

Debug builds store things for longer, because examining them is useful for debugging.

When release builds do store things in the array of locals, they are also more likely to reuse slots within the method.

This is then turned into machine code. It would be analogous in how it would work in that it would either produce the number 5, store it locally (on the stack or in a register) and then get rid of it again, or alternatively do nothing because the unused variable has been removed. (Perhaps not even executing the method; the method could be inlined, and then since it doesn't do anything be effectively removed entirely).

With a type with a constructor, there's slightly more going on:

.method public hidebysig instance void myMethod () cil managed 
{
  .locals init ([0] class Temp.Program/Customer)       // Set-up space for a reference to a Customer

  nop                                                  // Do nothing.
  newobj instance void SomeNamespace/Customer::.ctor() // Call Customer constructor (results in Customer on the stack)
  stloc.0                                              // Store the customer in the frist slot in locals
  ret                                                  // Return
}

.method public hidebysig instance void myMethod () cil managed 
{
  newobj instance void SomeNamespace/Customer::.ctor() // Call Customer constructor (results in Customer on the stack)
  pop                                                  // Remove value from stack
  ret                                                  // Return
}

Here both call the constructor, and even the release build does that, because it has to ensure that any side-effects still happen.

There is also more happening if Customer is a reference type. If it's a value-type then all of it is held in the stack (though it may have fields that are reference types in turn). If it's a reference type then what is held in the stack is a reference to an object in the heap. When there are no longer any such references on the stack the garbage collector won't find it in its sweep to find which objects it can't collect, and it can be collected.

In the release version, there might never be a memory location or register that holds that reference once the constructor returns. Indeed, there might not be one even when the constructor was running (if no field accesses or other implicit or explicit use of this happen) or it might have been wiped part-way through that (once such accesses had finished), so garbage collection could happen before the constructor has even finished.

More likely it will hang around in heap memory for some time after the method has returned, because the GC hasn't run yet.




回答2:


In the first case, number is a value type and will be stored on the stack. As soon as you leave the method, it'll no longer exist. And there is no deallocation going on, the stack space will simply be used for other things.

In the second case, since Customer (I'd assume) is a reference type, myClient would store a reference to the instance on the stack. As soon as you leave the method, that reference no longer exists. And this means that the instance will eventually be garbage collected.




回答3:


Assuming you're running under Debug, with no optimizations on:

When it goes outside of code block (line 5) what happends to variable number?

The value is popped out of the stack once the method exits. The fact that value types live on the stack is an implementation detail, and you shouldn't rely on that. If this was a value type which was a field in a class, it wouldn't live on the stack but on the heap.

When it goes outside of code block (line 5) what happends to variable number?

Assuming Customer is a class and not a struct, and has no finalizer implemented (which changes the course of things), it will no longer have any object referencing it after line 9. Once GC kicks in (at an arbitrary, non deterministic time), it will see it as eligible for collection and mark it as so during the mark phase. Once sweep phase starts, it will free the occupied memory.




回答4:


In 99% of the cases, the answer is "it doesn't matter". The only thing that matters is that it's no longer accessible to you.

You shouldn't care too often about the remaining 1%. It's not easy to simplify this enough for a reasonable answer on SO. The only thing I can say simply enough is:

  • As soon as a variable is no longer used in the future, it's perfectly legal for the compiler or the runtime to do whatever the hell it pleases :)

Note that this doesn't mention anything about scope - C# actually doesn't care about scope all that much. Scope is there to help you code, not to help the compiler (although the method-and-higher scoping certainly helps compilation times).

Again, in most cases, you don't care what happens next. The main exceptions to this are:

  • Using unmanaged resources. It's usually a good idea to dispose of unmanaged resources deterministically. All the classes that encapsulate unmanaged resources have a Dispose method to handle this. You can use the using statement to help with this.
  • Performance bottle-necks - if profiling shows you you're losing memory/CPU on impractical deallocation, you might want to help a bit.
  • Keeping a reference to an object outside of the scope. It's quite easy to accidentally prevent collection of something that's no longer used, but still has a reference. This is the main cause of memory leaks in managed applications.

Also, if you get around to playing around with this a bit, note that by default, having a debugger attached is going to mess with you a bit. For example, locals will be kept alive until their scope ends - this is of course perfectly legal, and it helps when debugging.




回答5:


When reference to a field of structural type is lost - memory is released (in stack). For reference type it's more complicated. If object (class) is no longer in use and reference to it is lost then it's marked for deletion by Garbage Collector. If nothing changes this object is deleted upon next garbage collection.

If you don't want to wait for GC to run it's method automatically you can do it by yourself by invoking GC.Collect() method

P.S. Before your class object is destructed and memory is released (if it implements IDisposable interface) it calls three methods one after another :

 1. Dispose() 2. Finalize() 3. ~ctor()

In C# you can use two of them: dispose() and finalize(). Dispose is generally used for freeing managed resources (for example FileStream or Threads) when Finalize is more suitable to write logic for unmanaged resources deallocation.

To change logic for object.Finalize() method - put your logic into ~ctor() But be careful with that as it may lead to some serious malfunctions.



来源:https://stackoverflow.com/questions/31137225/variable-lifetime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!