I have following code.
public class ClassTest
{
public int Id { get; set; }
public int OtherId { get; set; }
}
public struct StructTest
{
public int Id { get; set; }
public int OtherId { get; set; }
}
static void Main(string[] args)
{
ClassTest c = new ClassTest() { Id = 1, OtherId = 2 };
StructTest s = new StructTest() { Id = 51, OtherId = 52 };
Console.WriteLine("Attach the debugger now.");
Console.ReadKey();
GC.KeepAlive(s);
Console.WriteLine("Done");
}
I attach windbg when application is waiting for the ReadKey call. Then I run following commands.
0:004> !DumpHeap -stat
Statistics:
MT Count TotalSize Class Name
000007fee1419b10 1 24 System.IntPtr
000007fee1416090 1 24 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]]
000007fee140a1a8 1 24 System.Reflection.Missing
000007fee140a060 1 24 System.__Filters
000007fee14090a0 1 24 System.Reflection.__Filters
000007fee1408ab0 1 24 System.Attribute[]
000007fee1408978 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.RuntimeType, mscorlib]]
000007fee1408058 1 24 System.Security.HostSecurityManager
000007fee14074a8 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.Type, mscorlib]]
000007fe82c85bd8 1 24 ConsoleApplication1.ClassTest
000007fee145c360 1 32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
000007fee145c2d0 1 32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
000007fee1415ba8 1 32 System.Version
000007fee14155c0 1 32 Microsoft.Win32.SafeHandles.SafeFileHandle
000007fee140d548 1 32 System.Reflection.RuntimePropertyInfo[]
000007fee1408240 1 32 System.Runtime.Versioning.TargetFrameworkAttribute
000007fee1407fe0 1 32 System.Security.Policy.Evidence+EvidenceLockHolder
000007fee1407188 1 32 System.Security.Policy.AssemblyEvidenceFactory
000007fee1407040 1 32 Microsoft.Win32.SafeHandles.SafePEFileHandle
000007fee140a570 1 35 System.Boolean[]
000007fee145c558 1 40 Microsoft.Win32.Win32Native+InputRecord
000007fee145c1c8 1 40 System.Text.InternalEncoderBestFitFallback
000007fee145bce0 1 40 System.IO.Stream+NullStream
000007fee140d6e8 1 40 System.Reflection.CerHashtable`2+Table[[System.String, mscorlib],[System.Reflection.RuntimePropertyInfo[], mscorlib]]
000007fee145cad0 1 48 System.Text.EncoderNLS
000007fee145c7b8 1 48 System.IO.TextWriter+SyncTextWriter
000007fee145c258 1 48 System.Text.InternalDecoderBestFitFallback
000007fee1416ee0 1 48 System.Text.UTF8Encoding
000007fee1411db0 1 48 System.SharedStatics
000007fee140b030 2 48 System.Reflection.ParameterInfo[]
000007fee1408140 1 48 System.AppDomainManager
000007fee145c5d8 1 56 System.IO.__ConsoleStream
000007fee1415058 1 56 System.Text.UnicodeEncoding
000007fee140d4d8 1 56 System.RuntimeType+RuntimeTypeCache+MemberInfoCache`1[[System.Reflection.RuntimePropertyInfo, mscorlib]]
000007fee140c4b8 1 56 System.RuntimeType+RuntimeTypeCache+MemberInfoCache`1[[System.Reflection.RuntimeMethodInfo, mscorlib]]
000007fee1415460 2 64 System.Text.DecoderReplacementFallback
000007fee14153d0 2 64 System.Text.EncoderReplacementFallback
000007fee1412df8 1 64 System.Security.PermissionSet
000007fee1407f60 1 64 System.Threading.ReaderWriterLock
000007fee14070e8 1 64 System.Security.Policy.PEFileEvidenceFactory
000007fee1413e98 3 72 System.Int32
000007fee1412c60 1 72 System.Security.Policy.Evidence
000007fee1415668 1 80 System.Collections.Hashtable
000007fee140d758 1 80 System.Reflection.RuntimePropertyInfo[][]
000007fee0da09e0 1 80 System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Globalization.CultureData, mscorlib]]
000007fee0da0030 1 80 System.Collections.Generic.Dictionary`2[[System.RuntimeType, mscorlib],[System.RuntimeType, mscorlib]]
000007fee0d9fee8 1 80 System.Collections.Generic.Dictionary`2[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]]
000007fee1416268 1 96 System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Globalization.CultureData, mscorlib]][]
000007fee1415710 1 96 System.Collections.Hashtable+bucket[]
000007fee1412bb0 1 96 System.Threading.Thread
000007fee140e0e0 1 96 System.RuntimeMethodInfoStub
000007fee1409438 4 96 System.UInt16
000007fee140d2b0 1 104 System.Reflection.RuntimePropertyInfo
000007fee1409208 1 104 System.IO.UnmanagedMemoryStream
000007fee1416958 1 112 System.IO.StreamWriter
000007fee1414a98 2 112 System.Reflection.RuntimeAssembly
000007fee140c528 3 120 System.Reflection.RuntimeMethodInfo[]
000007fee145bfc8 1 128 System.Text.SBCSCodePageEncoding
000007fee14125d0 1 128 System.AppDomainSetup
000007fee1409140 2 128 System.Reflection.TypeFilter
000007fee1408b28 2 128 System.Reflection.RuntimeModule
000007fee1407530 2 128 System.Type[]
000007fee1415dc0 3 144 System.Text.StringBuilder
000007fee1415d50 1 160 System.Globalization.CalendarData
000007fee1411bc0 1 160 System.ExecutionEngineException
000007fee1411b48 1 160 System.StackOverflowException
000007fee1411ad0 1 160 System.OutOfMemoryException
000007fee14118e8 1 160 System.Exception
000007fee1409ba8 1 160 System.RuntimeType+RuntimeTypeCache
000007fee1411c98 7 168 System.Object
000007fee1406fe0 2 168 System.Runtime.Versioning.TargetFrameworkAttribute[]
000007fee140a118 3 192 System.Reflection.MemberFilter
000007fee1408340 4 192 System.RuntimeType[]
000007fee1415cd8 1 208 System.Globalization.CalendarData[]
000007fee14164b0 1 216 System.Globalization.NumberFormatInfo
000007fee1411e70 1 216 System.AppDomain
000000000053bd50 8 216 Free
000007fee140c1b0 2 224 System.Reflection.RuntimeMethodInfo
000007fee140ae30 3 240 System.Signature
000007fee14093d0 1 281 System.Byte[]
000007fee1408840 1 288 System.Collections.Generic.Dictionary`2+Entry[[System.RuntimeType, mscorlib],[System.RuntimeType, mscorlib]][]
000007fee1411c38 2 320 System.Threading.ThreadAbortException
000007fee1408d70 1 360 System.Reflection.CustomAttributeRecord[]
000007fee14157f0 3 384 System.Globalization.CultureInfo
000007fee1407e28 3 720 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
000007fee1413e30 12 764 System.Int32[]
000007fee1412860 8 932 System.Char[]
000007fee1412aa8 19 1296 System.String[]
000007fee1415b50 3 1608 System.Globalization.CultureData
000007fee1413698 58 3248 System.RuntimeType
000007fee14116b8 176 8394 System.String
000007fee1411d30 8 35280 System.Object[]
Total 412 objects
0:004> ~0s
ntdll!ZwRequestWaitReplyPort+0xa:
00000000`7708bf5a c3 ret
0:000> !CLRStack
OS Thread Id: 0x453c (0)
Child SP IP Call Site
000000000030e998 000000007708bf5a [InlinedCallFrame: 000000000030e998] Microsoft.Win32.Win32Native.ReadConsoleInput(IntPtr, InputRecord ByRef, Int32, Int32 ByRef)
000000000030e998 000007fee19b9781 [InlinedCallFrame: 000000000030e998] Microsoft.Win32.Win32Native.ReadConsoleInput(IntPtr, InputRecord ByRef, Int32, Int32 ByRef)
000000000030e960 000007fee19b9781 *** WARNING: Unable to verify checksum for C:\windows\assembly\NativeImages_v4.0.30319_64\mscorlib\f89061884b75dab0e3967d7221e5290d\mscorlib.ni.dll
DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr, InputRecord ByRef, Int32, Int32 ByRef)
000000000030ea70 000007fee1a86e26 System.Console.ReadKey(Boolean)
000000000030eb60 000007fe82d90547 *** WARNING: Unable to verify checksum for c:\users\james\documents\visual studio 2015\Projects\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe
ConsoleApplication1.Program.Main(System.String[]) [c:\users\james\documents\visual studio 2015\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs @ 16]
000000000030ee30 000007fee2386a53 [GCFrame: 000000000030ee30]
0:000> !dso
OS Thread Id: 0x453c (0)
RSP/REG Object Name
000000000030EAB0 0000000002387670 System.Object
000000000030EAB8 0000000002384518 System.String Attach the debugger now.
000000000030EB40 0000000002384500 System.String[]
000000000030EBA0 0000000002384590 ConsoleApplication1.ClassTest
000000000030EBA8 0000000002384590 ConsoleApplication1.ClassTest
000000000030EC00 0000000002384500 System.String[]
000000000030ECE8 0000000002384500 System.String[]
000000000030EDA8 0000000002384500 System.String[]
000000000030EDB0 0000000002382518 System.RuntimeType
000000000030EDF8 0000000002382e50 System.RuntimeType
000000000030EF78 0000000002384500 System.String[]
000000000030EFA0 0000000002381658 System.AppDomain
000000000030F088 0000000002381658 System.AppDomain
000000000030F0E0 0000000002383bb8 System.String .NETFramework,Version=v4.0
000000000030F258 0000000002381658 System.AppDomain
000000000030F518 0000000002381440 System.SharedStatics
My question is why I don't see any instance of StructTest anywhere in the heap or thread stack objects?
Your object is obviously not on the heap. You can dump stack objects with !dso
, but this will not reveal it either. The reason is that it is compiled by the JIT compiler to use a register only:
0:000> !clrstack
OS Thread Id: 0x1de4 (0)
Child SP IP Call Site
003defb0 74cc7f8e [InlinedCallFrame: 003defb0]
003defac 7189d80b DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr, InputRecord ByRef, Int32, Int32 ByRef)
003defb0 7193616a [InlinedCallFrame: 003defb0] Microsoft.Win32.Win32Native.ReadConsoleInput(IntPtr, InputRecord ByRef, Int32, Int32 ByRef)
003df038 7193616a System.Console.ReadKey(Boolean)
003df0c0 001a048a StructNotInHeap.Program.Main(System.String[]) [F:\Debugging\Source\StackOverflow\StructNotInHeap\Program.cs @ 22]
003df248 722fea96 [GCFrame: 003df248]
0:000> !U /d 001a048a
Normal JIT generated code
StructNotInHeap.Program.Main(System.String[])
Begin 001a0448, size 70
[...]
F:\Debugging\Source\StackOverflow\StructNotInHeap\Program.cs @ 22:
001a0469 b833000000 mov eax,33h
001a046e 8d5001 lea edx,[eax+1]
Of course, Line 22 is
StructTest s = new StructTest() { Id = 51, OtherId = 52 };
mov eax, 33h
moves 0x33 (or 51 decimal) to the EAX register and thus initialized the Id
property. lea edx,[eax+1]
is a smart way to store EAX+1 (or 52 decimal) to the EDX register, thus OtherId
is initialized. It's smart because it uses the adressing pipeline instead of the algorithmic unit of the CPU.
Digging further into it, it seems !dumpheap
never lists unboxed value types, even if they are allocated on the heap along with an object. You need the !DumpVC
command to see value types which are part of an object on the heap.
If they are boxed, you can find them on the heap. Use your commands with this program:
class Program
{
public class MyClass
{
public StructTest MyStruct;
}
public struct StructTest
{
public int Id { get; set; }
public int OtherId { get; set; }
}
private static void Main()
{
var c = new MyClass();
c.MyStruct.Id = 51;
c.MyStruct.OtherId = 52;
object boxed = c.MyStruct;
Console.WriteLine("Attach the debugger now.");
Console.ReadKey();
Console.WriteLine(c.MyStruct.Id.ToString() + boxed);
}
}
In this boxed case, you can also find the reference to it on the stack (!dso
).
In Release build, things get optimized quite far. Until 64 bytes size I was not able to see the struct on the stack (in a 32 bit program). When I exceeded the 64 bytes limit, the struct was allocated on the stack, but still not visible in !dso
.
class Program
{
public struct StructTest
{
public long Id { get; set; }
public long OtherId { get; set; }
public long MoreSpace { get; set; }
public long EvenMoreSpace { get; set; }
public long MoreThan64Byte { get; set; }
}
private static void Main()
{
var c = new StructTest();
c.Id = 51;
c.OtherId = 52;
c.MoreSpace = 53;
c.EvenMoreSpace = 54;
c.MoreThan64Byte = 55;
Console.WriteLine("Attach the debugger now.");
Console.ReadKey();
Console.WriteLine(c.Id + c.OtherId + c.MoreSpace + c.EvenMoreSpace);
}
}
This gets compiled to
001a045e 0f57c0 xorps xmm0,xmm0
001a0461 660fd607 movq mmword ptr [edi],xmm0
001a0465 660fd64708 movq mmword ptr [edi+8],xmm0
001a046a 660fd64710 movq mmword ptr [edi+10h],xmm0
001a046f 660fd64718 movq mmword ptr [edi+18h],xmm0
001a0474 660fd64720 movq mmword ptr [edi+20h],xmm0
F:\Debugging\Source\StackOverflow\StructNotInHeap\Program.cs @ 19:
001a0479 c745d433000000 mov dword ptr [ebp-2Ch],33h
001a0480 c745d800000000 mov dword ptr [ebp-28h],0
F:\Debugging\Source\StackOverflow\StructNotInHeap\Program.cs @ 20:
001a0487 c745dc34000000 mov dword ptr [ebp-24h],34h
001a048e c745e000000000 mov dword ptr [ebp-20h],0
F:\Debugging\Source\StackOverflow\StructNotInHeap\Program.cs @ 21:
001a0495 c745e435000000 mov dword ptr [ebp-1Ch],35h
001a049c c745e800000000 mov dword ptr [ebp-18h],0
F:\Debugging\Source\StackOverflow\StructNotInHeap\Program.cs @ 22:
001a04a3 c745ec36000000 mov dword ptr [ebp-14h],36h
001a04aa c745f000000000 mov dword ptr [ebp-10h],0
F:\Debugging\Source\StackOverflow\StructNotInHeap\Program.cs @ 23:
001a04b1 c745f437000000 mov dword ptr [ebp-0Ch],37h
001a04b8 c745f800000000 mov dword ptr [ebp-8],0
Which is the initialization first and then moving values to the stack (EBP is the current stack pointer).