C#System.String实例是否真的在堆上结束?

Mih*_*ert 7 c# stack cil heap-memory .net-assembly

让我们考虑一些非常简单的C#代码:

static void Main(string[] args)
        {
            int i = 5;
            string s = "ABC";
            bool b = false;
        }
Run Code Online (Sandbox Code Playgroud)

Jeffrey Richter的“ 通过C#进行CLR ”(第14章)指出,“ String类型是直接从Object派生的,使其成为引用类型,因此,String对象(其字符数组)始终存在于堆中,而不是在线程的堆叠 ”。

在书中的一个示例上也与上面的示例非常相似,它也涉及字符串:“ newobj IL指令构造了一个对象的新实例。但是,IL代码示例中没有newobj指令出现。相反,您看到了特殊的ldstr (负载字符串)IL指令,其通过使用从元数据得到的文字串构成一个字符串对象。这显示了公共语言运行库(CLR)不,事实上,具有构造文字串对象的一种特殊的方式。

查看IL代码,显然是这种情况(仅显示了相关部分):

[...]
    .locals init (
        [0] int32,
        [1] string,
        [2] bool
    )
    // (no C# code)
    IL_0000: nop
    // int num = 5;
    IL_0001: ldc.i4.5
    IL_0002: stloc.0
    // string text = "ABC";
    IL_0003: ldstr "ABC"
    IL_0008: stloc.1
    // bool flag = false;
[...]
Run Code Online (Sandbox Code Playgroud)

所述ldstrIL指令确保了的对象引用的字符串被压入堆栈 ”。这很有意义-字符串的实例保留在堆上,并且对该对象(其地址)的引用由变量存储在堆栈上。

现在,让我们在text要声明的变量之后的行上设置一个断点,在Visual Studio中开始调试,然后切换到“反汇编”视图。相关代码如下(完整的反汇编代码在此处):

017B0483  nop  
            int i = 5;
017B0484  mov         dword ptr [ebp-40h],5  
            string s = "ABC";
017B048B  mov         eax,dword ptr ds:[429231Ch]  
017B0491  mov         dword ptr [ebp-44h],eax  
            bool b = false;
017B0494  xor         edx,edx  
017B0496  mov         dword ptr [ebp-48h],edx  
        }
Run Code Online (Sandbox Code Playgroud)

在2个汇编指令处理C#寻找具体string行,第一个移动的虚拟内存在内容429231Ceax寄存器,和所述第二存储堆叠,所述其中在相应的内容s可变生命。

让我们使用WinDbg(x86,因为C#代码使用VS的默认32位目标平台)通过以非侵入方式附加到VS调试的进程来查看该特定地址。429231C上面的内容应该是对字符串实际所在的存储空间的引用。让我们检查:

在此处输入图片说明

第二个命令的确会产生一个414243以十六进制表示ABC以ASCII 表示;但是顺序并不正确,可能只是巧合。(1)看起来好像字符串行的汇编代码没有做对。

如果我们使用VMMap查看该地址: 在此处输入图片说明

原始地址429231C看起来在托管堆中。但是,然后(2)为什么将堆中地址的内容作为包含在堆栈变量中的引用引入,如先前的汇编代码所示?

2问题小号我问是(1)和(2)。尽管事实上在分析IL代码之前一切对我来说都是有意义的,但是一旦我查看了该IL的反汇编代码,事情就会很快走下坡路。我倾向于以为我在逻辑上搞砸了(很可能),或者在VS调试器中碰到了某种错误(不太可能)。

以后的更新:正如@madreflection和@Jester所指出的那样,字节序使我绊倒了。十六进制表示可以检出所有内容。现在仅保留问题(2)

以后的更新2:评论非常有见地,我认为@madreflection最好说-有一个间接的附加级别-这样做的原因(评论中指出)现在对我来说很有意义。下面是一个快速图表。我还检查了两个地址是否确实属于VMMap的托管堆。

在此处输入图片说明

以后的更新3:更正了之前的图。