6

I'm refreshing my memory on how reference and value types work in .NET. I understand that the entry on the stack for a reference type contains a pointer to a memory location on the heap. What I can't seem to find details about is what else the stack entry contains. So, given the following:

Customer customer;
customer = new Customer();

After the first line of code an entry on the stack containing a null pointer will exist. Does that entry also contain the identifying name "customer"? Does it contain type information?

Have I fundamentally misunderstood something?

Pseudonymous
  • 839
  • 5
  • 13
  • 3
    There are already good answers here, so I'll just add my thoughts. A good way to understand what's happening is to look at the MSIL. Use the ILDASM tool to view the MSIL for your compiled assembly and you'll learn a lot. – Spivonious Jun 21 '16 at 13:30

3 Answers3

11

Only the pointer to the object is stored, nothing else. Not on the stack, it is stored in a processor register. Only if the method is large and the register is better used for other purposes will it be stored on the stack. Making that the decision is the job of the optimizer. Which is in general fairly unlikely to happen since you'll probably be using properties and methods of the Customer class so keeping the pointer in a register is efficient.

This is otherwise the basic reason why a NullReferenceException can tell you nothing useful about what reference is null. Associating the pointer value back to a named "customer" variable can only be done by a debugger. It needs the PDB file to know the name of the variable, that name is not present in the metadata of the assembly. Which is the basic reason why you cannot use Reflection to discover local variable names and values.

And this is also why debugging only works well when you use it on the Debug build of your project. That disables the optimizer and gets the pointer value to always be stored back to a stack slot, allowing the debugger to reliably read it back. At a significant cost, the basic reason you should only ever deploy the Release build of your project.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Appreciate the detailed answer. Just to clarify: by execution time the notion of a variable named "customer" is no longer relevant and is only preserved by a debugger utilising a .PDB file? And also, if the optimiser is in play, the pointer to the "customer" object may have never even been on the stack in the first place? It could have been stored only in a processor register? – Pseudonymous Jun 21 '16 at 13:49
  • Yes, that's what the post says. – Hans Passant Jun 21 '16 at 13:54
2

The variable name customer does only exist in the source code (as it is of no interest to your computer). During runtime, nobody knows how the variable was called in the code.

Jon Skeet wrote a good article about what is stored on the heap and what goes to the stack. The first paragraph (what's in a variable?) should answer your question:

The value of a reference type variable is always either a reference or null. If it's a reference, it must be a reference to an object which is compatible with the type of the variable. For instance, a variable declared as Stream s will always have a value which is either null or a reference to an instance of the Stream class. (Note that an instance of a subclass of Stream, eg FileStream, is also an instance of Stream.) The slot of memory associated with the variable is just the size of a reference, however big the actual object it refers to might be. (On the 32-bit version of .NET, for instance, a reference type variable's slot is always just 4 bytes.)

Marco7757
  • 735
  • 9
  • 16
  • Thanks for the link. I think some of the answer is implied in _"the slot of memory associated with the variable is just the size of a reference"_ which suggests a stack entry is literally just a pointer to a memory address. In that case, what is guaranteeing that it only ever is `null` or pointing to a `Stream` object? – Pseudonymous Jun 21 '16 at 13:59
  • I guess nobody is guaranteeing that the instance the pointer is pointing to is a `Stream` object other than your compiler. If you would change the reference during runtime to point to some other type than `Stream` your program would crash. It just relies on you/your compiler to give it a correct data type. I am not sure, however. Somebody correct me if I am wrong! – Marco7757 Jun 21 '16 at 14:56
  • Your last comment is correct. The type of a declaration only matters at compile time, that's when they are checked and enforced. If you edit the generated IL after compilation, you can put any pointer you want in there, or an invalid pointer. Same thing, of course, with code injection/rewriting at run-time. That said, I'm not really sure what Jon Skeet's words (as quoted in your answer) have to do with this question. – Cody Gray - on strike Jun 22 '16 at 06:11
  • Well, it answers how large the slot for a variable is on the stack and that only a reference is stored. – Marco7757 Jun 22 '16 at 10:52
2

1- Variable names are not stored in memory along with the reference.

2- A null reference doesn't store the type of the object.

Example to make things clear:

string str = null;

if(str is String)
{
    Console.WriteLine("I'm a string");
}
else
{
    Console.WriteLine("I'm not a string");
}

// This will print: "I'm not a string"

You're actually telling the compiler, allow me to create only string references using the mapped storage location by str.

Variables represent storage locations. Every variable has a type that determines what values can be stored in the variable

Zein Makki
  • 29,485
  • 6
  • 52
  • 63