为什么结构体实例不在堆上或栈上呢?

3
我有以下代码。
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");
        }

当应用程序等待ReadKey调用时,我会附加Windbg。然后执行以下命令。

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

我的问题是为什么在堆或线程堆栈对象中没有看到 StructTest 的任何实例?


1
你的结构体只有大约64位大小,而s只是一个局部变量,所以我猜它只存储在一个寄存器中。不需要将其放在堆栈或堆中。 - René Vogt
我该如何将它放入堆栈中?是否有任何大小限制? - jim crown
4
一般而言,在托管代码中,您不需关心存储位置。内存管理由框架或CLI完成,与您无关。请注意,此处的“托管代码”指安全代码。 - René Vogt
@RenéVogt 我理解这一点,但我也想要学习内部机制。 - jim crown
1
@RenéVogt:直到你遇到内存碎片化问题、垃圾回收问题和其他问题之前。我认为学习 .NET 内存管理的工作原理,包括 OP 请求的结构体理解,是一个非常好的主意。 - Thomas Weller
显示剩余5条评论
1个回答

4

你的情况

你的对象显然不在堆上。你可以使用!dso转储栈对象,但这也无法揭示它。原因是它由JIT编译器编译为仅使用寄存器:

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]
当然,第22行是
StructTest s = new StructTest() { Id = 51, OtherId = 52 };

mov eax, 33h将0x33(或十进制的51)移动到EAX寄存器中,从而初始化了Id属性。lea edx,[eax+1]是一种聪明的方式,将EAX + 1(或十进制的52)存储到EDX寄存器中,从而初始化了OtherId。这很聪明,因为它使用了CPU的寻址流水线而不是算法单元。

堆上的结构体

深入研究后发现,即使未装箱的值类型与对象一起分配在堆上,!dumpheap也永远不会列出它们。您需要使用!DumpVC命令才能查看作为堆上对象一部分的值类型。

如果它们被装箱,您可以在堆上找到它们。 使用此程序执行您的命令:

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);
    }
}
在这个封闭的情况下,您还可以在堆栈上找到对它的引用(!dso)。

堆栈上的结构体

在发布版本中,优化得非常彻底。在64字节大小之前,我无法在堆栈上看到结构体(在32位程序中)。当我超过64字节限制时,结构体被分配在堆栈上,但在!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);
    }
}
这将被编译为:
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

首先进行初始化,然后将值移动到堆栈上(EBP是当前堆栈指针)。


2
@jimcrown:是的,你的本地结构在这种情况下没有存储在堆栈上,因为优化允许它在寄存器中。你问:“为什么我在堆或线程堆栈对象中看不到StructTest的任何实例?”我已经解释了。你还想知道什么? - Thomas Weller
1
@jimcrown:好的,我明白了。也许我可以举个例子。让我试试看... - Thomas Weller
1
@ThomasWeller 首先,你的陈述没有指定本地变量,但是,一个本地变量可以在寄存器中而不是堆栈中,如果它在async方法、迭代器块中或者被匿名方法所引用,编译器/即时编译器可能会完全消除它,它将被提升为类的字段。 - Servy
1
@jimcrown:好的,找到了。已更新答案。 - Thomas Weller
1
你看不到它,因为它不是一个对象。值类型(结构体)的成员被存储在内联中。例如,对于一个有两个整数成员的结构体,存储空间被分配在堆栈上或者使用该结构体的对象内部来存储这两个整数。这就是值类型和引用类型之间的区别。引用类型是堆上的对象,你可以拥有一个指针指向它。值类型的成员与使用它的类、函数等一起存储在内联中。 - Steve Johnson
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,