簡體   English   中英

Weakreferences和空條件運算符的怪異C#GC行為

[英]Weird C# GC behavior for Weakreferences and the null-conditional operator

在為與WeakReferences一起使用的C#代碼創建單元測試時,我遇到了一些怪異的GC行為-怪異的,因為我無法對此做出解釋。

該問題源於使用?. 從GC是要從我的弱引用中獲取的對象上獲取的空條件運算符。

這是復制它的最少代碼:

    public class XYZClass
    {
        public string Name { get; set; }
    }

    public class Tests
    {
        public void NormalBehavior()
        {
            var @ref = new WeakReference<XYZClass>(new XYZClass { Name = "bleh" });

            GC.Collect();
            GC.WaitForPendingFinalizers();

            XYZClass t;
            @ref.TryGetTarget(out t);

            Console.WriteLine(t == null); //outputs true
        }

        public void WeirdBehavior()
        {
            var @ref = new WeakReference<XYZClass>(new XYZClass { Name = "bleh" });

            GC.Collect();
            GC.WaitForPendingFinalizers();

            XYZClass t;
            @ref.TryGetTarget(out t);

            Console.WriteLine(t == null); //outputs false
            Console.WriteLine(t?.Name == null); //outputs false
        }
    }

使用linqpad運行此代碼時,此行為沒有表現出來。 我還檢查了已編譯的IL代碼(使用linqpad),但仍然無法識別任何錯誤。

這與null條件運算符無關。 通過將其替換為普通成員訪問權限,您可以輕松地看到它:

Console.WriteLine(t == null); //outputs false
Console.WriteLine(t.Name == null); //outputs false

對新XYZClass對象的原始引用在調試版本中永遠不會超出范圍(並在調試器下運行)。 在LINQPad中關閉優化功能,您還將看到t不為null。 但是請注意,所有這些都是實現細節-根據系統的具體情況,您可能會得到兩種結果(例如,我得到的是32位Debug構建,而不是64位Debug構建)。

關於.NET中托管對象生存期的唯一保證是,終結器外部的強引用將阻止對象的收集。 忘記所有確定性的內存管理-它只是不存在。 完全沒有垃圾收集器的.NET實現將是完全有效的。

因此,讓我們特別看看在我的機器上生成的代碼。 在64位版本中, t.Name == nullt?.Name == null具有完全相同的結果(盡管t.Name == null將導致NullReferenceException而不是返回true)。 那32位版本呢?

t.Name == null部分明顯較短:

00533111  mov         ecx,dword ptr [ebp-44h]   ; t
00533114  cmp         dword ptr [ecx],ecx  ; null check
00533116  call        00530D28  ; t.get_Name
0053311B  mov         dword ptr [ebp-54h],eax  ; Name string
0053311E  cmp         dword ptr [ebp-54h],0  ; is null?
00533122  sete        cl  
00533125  movzx       ecx,cl  
00533128  call        708B09F4  

您會看到我們使用了兩個寄存器(ecx和eax)和兩個堆棧插槽(-44h和-54h)。 t?.Name == null呢?

001F3111  cmp         dword ptr [ebp-44h],0   ; is t null?
001F3115  jne         001F311F  
001F3117  nop  
001F3118  xor         edx,edx  
001F311A  mov         dword ptr [ebp-54h],edx  ; result is false
001F311D  jmp         001F312A  
001F311F  mov         ecx,dword ptr [ebp-44h]  ; t
001F3122  call        001F0D28                 ; t.get_Name
001F3127  mov         dword ptr [ebp-54h],eax  
001F312A  cmp         dword ptr [ebp-54h],0    ; is name null?
001F312E  sete        cl  
001F3131  movzx       ecx,cl  
001F3134  call        708B09F4  
001F3139  nop  

我們仍然使用相同的兩個堆棧插槽,但是需要另一個寄存器-edx。 這可能是我們想要的嗎? 完全正確! 如果我們看一下對象最初是如何創建的:

001F30A0  mov         ecx,2C0814h  
001F30A5  call        001330F4  ; new XYZClass
001F30AA  mov         dword ptr [ebp-48h],eax  ; tmp
001F30AD  mov         ecx,dword ptr [ebp-48h]  
001F30B0  call        001F0D38  ; tmp.XYZClass()
001F30B5  mov         edx,dword ptr ds:[36B230Ch]  ; "bleh"
001F30BB  mov         ecx,dword ptr [ebp-48h]  
001F30BE  cmp         dword ptr [ecx],ecx  
001F30C0  call        001F0D30  ; tmp.set_Name("bleh")
001F30C5  nop  
001F30C6  mov         ecx,2C0858h  
001F30CB  call        710F9ECF  ; new WeakReference
001F30D0  mov         dword ptr [ebp-4Ch],eax  
001F30D3  mov         ecx,dword ptr [ebp-4Ch]  
001F30D6  mov         edx,dword ptr [ebp-48h]  ; EDX references tmp!
001F30D9  call        709090B0  
001F30DE  mov         eax,dword ptr [ebp-4Ch]  
001F30E1  mov         dword ptr [ebp-40h],eax  

您會發現,空條件版本使用的是用於保存對XYZClass的臨時引用的同一寄存器。 這就是差異的根源所在-運行時不能排除edx訪問是使用臨時引用,因此它可以安全地運行並保持對象的根目錄,從而防止收集該對象。

64位版本(並且在未連接調試器的情況下運行)沒有什么區別,因為它重用了不同的寄存器-在我的特定機器上,64位版本重用了rcx (它包含對WeakReference ,而不是XYZClass ) ,並且非調試器的32位版本重復使用eax (其中包含對"bleh"的引用)。 由於edx (和rdx )從未在該方法中使用,因此臨時引用不再植根,可以自由收集。

為什么調試器版本特別使用edx 最有可能的是,它試圖有所幫助。 在空條件運算符的中間,您希望同時看到tt?.Name ,以便更好地訪問它們(您可以在Locals中將其作為“ XYZClass.Name.get返回的“ bleh”字符串”查看)。 。

同樣,請注意,這完全是特定於實現的。 該合同只規定當一個對象不能被回收-它並沒有說何時被回收。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM