0

Going through documentation and theories I understand that Object variables are reference variables that stores the memory address of the actual data that is stored in the Heap.

Also I can see the same being reflected in articles from a few years back:

[Screenshot showing obj variable containing another memory address as its value]

Screenshot showing obj variable containing another memory address as its value

But spinning up Visual Studio 2022 I tried similar thing but now the Object variable I can see is directly containing the Object values:

[Screenshot showing object variable person containing value] Screenshot showing object variable person containing value directly

Can some kindly direct me in the right direction to understand if anything has changed fundamentally in C#?

Does this mean Object variables currently store data in the stack?

AKronym
  • 63
  • 7
  • nothing has changed fundamentally in C# or .NET types! – Falco Alexander Mar 22 '23 at 08:13
  • 1
    Fussing about where in memory objects are stored is to completely miss the point of using a managed language with garbage collection. You can literally spend all day every day writing decent C# code and never need to care about *where* things are stored. – Damien_The_Unbeliever Mar 22 '23 at 08:20
  • You are dereferencing the address of `person` so the debugger shows you the object this points to. I'm not sure I understand the issue here, but to answer your overall question: objects are not on the stack. They are on the heap. – Brian Rasmussen Mar 22 '23 at 08:21
  • @BrianRasmussen the issue is that *&[object_variable] contains different types of data in different versions of .Net. 1st one is having address values, 2nd one is having the direct object value – AKronym Mar 22 '23 at 08:28
  • What is the type definition for these two objects… you are not running the same code in each instance. Is Test a struct or a class? More importantly why do you care? – Matthew Whited Mar 22 '23 at 10:44

2 Answers2

2

the issue is that *&[object_variable] contains different types of data in different versions of .Net.

No, the issue is that you misinterpret the behavior of the Watch window in the different versions of Visual Studio. The memory layout of managed objects hasn't been changed since the beginning:

A managed reference is represented by the TypedReference struct, though you hardly ever need to use it directly (especially since we have strongly typed ref locals and return types) but in general you can use it to work with a managed reference itself. Its 1st field points to the object instance on the heap, but very strictly speaking the pointed address is another pointer just before the actual field data that points to the method table (this address is what you see in your first screenshot). VS2022 is just smart enough to recognize that you dereference a managed object instance, even though you did it by unmanaged pointers (which may get invalid if the object is relocated by the GC so you shouldn't rely on it without pinning).

In this answer (under Solution 2) you can see how you can exploit the TypedReference type to change the actual memory of a managed object instance but since .NET Core we have much more elegant solutions as well.

György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • "this is what stored on the stack" - not usually; usually it is *just* the reference, with the type data being conveyed via context in the IL - a TypedReference is a ref+type combo used for very specific and rare purposes; a TypedReference is twice the size of a regular reference – Marc Gravell Mar 22 '23 at 11:18
  • You are right, the actual JITted code is almost always stripped down to the pointers themselves. I could've been more precise. – György Kőszeg Mar 22 '23 at 11:20
  • the non-jitted IL code; also usually just the (managed) pointers; TypedReference: just doesn't make an appearance at all unless you're doing something very exotic – Marc Gravell Mar 22 '23 at 11:21
  • Alright, I removed the controversial part. I shouldn't have been so inaccurate. – György Kőszeg Mar 22 '23 at 11:29
  • honestly I think the statement "A managed reference is represented by the TypedReference struct" (without some extreme qualification) is equally controversial; "is" suggests this is the norm; in reality this is more of a "can be, in incredibly rare circumstances" – Marc Gravell Mar 22 '23 at 11:32
  • It _is_ a representation of references though. Maybe the missing part that could be emphasized better that `&obj` is illegal in such a C# code in the first place. And where `fixed` (or `GCHandle`) cannot be used (or is not the best idea), `TypedReference` is the way to access the reference as a pointer (not considering the `Unsafe` class, which is not always available, eg. in .NET Standard). – György Kőszeg Mar 22 '23 at 11:54
  • it isn't the representation that is normally used, though; "is" (without qualification) usually means similar to "always" or at least "typically", which this: isn't; also: "&obj is illegal in such a C# code" - no, `&obj` is absolutely not illegal; it just doesn't do anything related to the *object* - it means (literally) the address of the variable/parameter `obj`; finally, "TypedReference is the way to access the reference as a pointer" - no, not really; if you haven't pinned it first, **there is no valid way** to access a heap object: your pointer is immediately unreliable; and if you've – Marc Gravell Mar 22 '23 at 12:09
  • pinned it: the *pin* gives you that; it is perhaps moot, though, since accessing heap objects via unmanaged pointers: usually not an interesting thing to want to do when the regular managed pointer (aka what we think of as a reference, the *value* of the `obj` local): works perfectly; and for non-heap values, `in` and `ref` serve this purpose much more validly without all the indirection; the uses of `TypedReference` these days: not many, except for some very exotic (and undocumented) varargs scenarios – Marc Gravell Mar 22 '23 at 12:10
  • In the linked answer I _did_ use pinning. But `GCHandle` does not work if the object contains generics or non-blittable types and `fixed` cannot be used for simple objects. _" if you haven't pinned it first, there is no valid way to access a heap object"_ - there [is](https://github.com/koszeggy/KGySoft.CoreLibraries/blob/f5af53d716edd45d35e520c5e092ede71353afbb/KGySoft.CoreLibraries/CoreLibraries/_Extensions/TypeExtensions.cs#L920), but of course, you need to build in a retry mechanism. – György Kőszeg Mar 22 '23 at 12:32
  • And just before saying: yes, it uses `fixed`, too, but it doesn't pin first, only after we have the (potentially already invalid) pointer. – György Kőszeg Mar 22 '23 at 12:39
0

&obj is the address of the local variable (on the stack) - not the address of the object on the heap; everything you're showing seems most likely to be an IDE glitch with an unusual expression; *&obj is fundamentally the same thing as obj

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Or it was a glitch which was fixed in VS. – Guru Stron Mar 22 '23 at 11:15
  • @GuruStron hence "an IDE glitch", with VS being: the IDE – Marc Gravell Mar 22 '23 at 11:15
  • Yes, I understand that. Point being that based on OP the "glitchy" behavior was one which was present in earlier versions ("from few years back") of the IDE. – Guru Stron Mar 22 '23 at 11:17
  • @MarcGravell No, *&obj is not fundamentally the same thing as obj in C#. In C#, the & operator is used to obtain the memory address of a variable, which is also known as a pointer. The * operator is used to dereference a pointer, which means to access the value stored at the memory address pointed to by the pointer. – AKronym Mar 22 '23 at 14:53
  • @AKronym that pointer points to: the address of the local variable; dereferencing it gives us the *value* of that local variable, i.e. the reference which should point to the object on the heap; accessing `obj` *also* gives us the reference which should point to the object on the heap; sorry, but yes: `&*obj` is identical to `obj`, just: with extra steps. What do *you* imagine `&*obj` is doing, vs `obj`? sorry, but you're just wrong here: `*&foo === foo`; they mean *literally* the same thing (just: with indirection) – Marc Gravell Mar 22 '23 at 15:08
  • @AKronym interestingly, the decompiler tool used by sharplab even elides these "extra steps", i.e. shows the simpler version (even though the IL has the extra steps): https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA+ABAjEgBBgJlwAUBYAKAG8Ldb8dcBXAOwGcBDAMxnxVwFVmAW3bN2AcxgATAEIBPAGrsANgAoAYhAi5OWgJS4AvAD5c8pWoBUAMl0Q9Abhp1seFh269cAWVETp5ioaWjr6RqaBanaOzrSuTGxcPBh8giJikrJyAEownMHa0eFmOXmqsJy4NtFO5HT0bomeKT5+mfK5+ZqFYSYlneV5ofa19fEtkQXDepQAvrgA9Au4zBAALrgAlkIADtBromuxDV4dZRW43dNzi8urG9t7UAfMR3UuRFeUuLMUs0A== – Marc Gravell Mar 22 '23 at 15:36
  • @MarcGravell then can you explain what is happening in my first screenshot in the question? Why is *& giving memory address? – AKronym Mar 22 '23 at 15:36
  • @AKronym because as I said: most likely the IDE is doing a bad job, and switching into a memory address mode *incorrectly*, a glitch that now appears to be fixed; I'd be interested what that "type" column on the right says for that row, but it is cropped :( – Marc Gravell Mar 22 '23 at 15:37
  • @MarcGravell it says System.IntPtr – AKronym Mar 22 '23 at 15:39
  • @AKronym so it sounds like it is failing to track the types correctly, and so is **showing** the *raw value* of the reference, rather than understanding that it is an object reference and dereferencing it; but that's just a UI bug - if you typed `obj` and it showed you the raw heap pointer rather than the object, you'd consider that a bug; that is *exactly* what is happening here - the *meaning* of `*&` isn't changed by this bug - it has just done something silly when displaying it – Marc Gravell Mar 22 '23 at 15:44