5

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?

ExpertLoser
  • 257
  • 4
  • 14
  • 2
    http://stackoverflow.com/questions/5422918/how-does-garbage-collection-and-scoping-work-in-c – Tim Schmelter Jun 30 '15 at 11:40
  • I bet compiler will not even bother to do anything inside of first method. Second method will have to call constructor, but instance will not be stored anywhere because it's not used. – Sinatr Jun 30 '15 at 11:47
  • As for beginner programmer (which I am), garbage collection is not the topic for beginner. Therefore my question is legitimate, as beginners dont encounter garbage collection subject. I have structured question in a way that it is acceptable for beginners to understand as we mostly hear "its not in the scope" which does not clarify completely whats going on. – ExpertLoser Jun 30 '15 at 11:48
  • The main problem is that you shouldn't really care about what happens - that's the "managed" part of the language. You only really need to care when you start doing performance tweaking, and that really isn't a subject for a beginner. But do note that if your C# book/tutorial/sample tells you that the memory is released when the variable leaves scope, it's wrong (or at least misleading). The main point of scoping is to ease programming - you simply can't access the local anymore, so you don't have to keep thinking about it outside of the scope. – Luaan Jun 30 '15 at 12:02
  • 2
    I don't think this is quite a duplicate, it asks about bits other than the GC part (though that is also relevant). – Jon Hanna Jun 30 '15 at 12:13

5 Answers5

8

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.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
1

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.

Luaan
  • 62,244
  • 7
  • 97
  • 116
1

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.

Chris
  • 5,442
  • 17
  • 30
  • Unlikely. `number` will by far most likely be simply kept in a register. No need to do expensive stack manipulation for this. If it even gets "allocated" in the first place, that is :D – Luaan Jun 30 '15 at 11:45
  • Objects can be garbage collected before you leave the method. – Jon Hanna Jun 30 '15 at 11:48
  • You guys are of course both correct. But, for the sake of keeping things simple for beginners, I think my answer will do :) – Chris Jun 30 '15 at 11:54
1

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.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
1

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.

Fabjan
  • 13,506
  • 4
  • 25
  • 52