天天看點

【UE4_C++】 <3> 記憶體管理,智能指針以及調試

本章包含以下内容:
  • 非托管記憶體-malloc()/free()的使用
  • 非托管記憶體-new/delete的使用
  • 托管記憶體-NewObject<>和ConstructObject<>的使用
  • 托管記憶體-釋放記憶體
  • 托管記憶體-使用智能指針(TSharePtr,TWeakPtr,TAutoPtr)來跟蹤一個object
  • 使用TScopedPoint 來跟蹤一個object
  • UE的垃圾回收,以及UPROPERTY()
  • 斷點與單步執行代碼
  • 查找錯誤和使用調用堆棧

一、非托管記憶體-malloc()/free()的使用

添加代碼到GameMode中:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

并且在關卡藍圖中調用:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

運作結果如下:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

可見非托管記憶體的申請和釋放方法需要自己來處理

我們使用malloc()來申請記憶體,使用完畢後用free()來釋放

需要特别說明的是調用free()之後,指針會斷開與原來記憶體位址的連接配接,

i=0 則是将指針指向一個空引用,這是為了清除掉i指針,讓i指針不會去引用到任何有效記憶體片段

二、非托管記憶體-new/delete的使用

首先說一下malloc與new的差別:

  • new是運算符malloc是庫函數
  • new和delete底層還是由malloc和free實作的,差別在于new和delete在生成複雜資料類型時會執行兩步
    1. 配置設定記憶體和調用構造函數。
    2. 調用析構函數和銷毀記憶體。
  • malloc申請記憶體沒有類型為(void*)需要根據使用需求進行強制轉換,new不需要。
  • new并不是每次都會調用構造函數,對于簡單類型eg.int是不需要的

驗證一下:

添加代碼:

将Object類添加進GameMode中:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

添加方法:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

在關卡藍圖調用方法:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

運作結果如下:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

三、托管記憶體-NewObject<>和ConstructObject<>的使用

前面說到C++中非托管記憶體的申請和釋放使用new ,delete,malloc和free 來進行。

需要注意的是在系統中建立對象配置設定記憶體使用完畢之後不要忘記釋放,否則則會出現一些沒有使用的記憶體塊并且越來越多,就會引發記憶體洩漏而在UE4中自動使用托管記憶體,要在引擎中使用的對象的任何配置設定大部分都使用NewObject() 或SpawnActor()函數來完成

托管記憶體:防止遺忘釋放記憶體,在程式中,動态索引條目内容:

<indexentry content="managed memory:allocating, ConstructObject used">
           

配置設定了引用該對象的指針的數量。 如果沒有指向該對象的指針,則會立即自動删除該對象,或者在下一次運作垃圾收集器時标記該對象以便删除。

前面有提過在C++中執行個體化Object,這邊取當時的代碼:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

這裡,UAction: : StaticClass ()為 UAction 對象獲得一個基本的 UClass * 。 Newobject 的第一個參數是 GetTransientPackage () ,它隻檢索遊戲的 transient 包。 Ue4中的一個包(UPackage)隻是一個資料集合。 這裡我們使用 Transient 包來存儲堆配置設定的資料。 您還可以使用 Blueprints 中的 UPROPERTY () TSubclassOf aactor 來選擇 UClass 執行個體。

第三個參數(可選)是訓示記憶體管理系統如何處理 UObject 的參數組合。RF_* flags的詳細資訊可參考:

https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Objects/Creation/index.html#objectflags

還有一個非常類似于 NewObject 的函數名為 ConstructObject。 Constructobject 在構造中提供了更多的參數,如果需要初始化某些屬性,它很有用。 否則,NewObject 就足以完成功能。

四、托管記憶體-釋放記憶體

前面說過托管記憶體可以自動處理釋放記憶體。當不再有對 UObject 執行個體的引用時,将對 UObject 執行個體進行引用計數和垃圾回收。 其實使用 ConstructObject 或 NewObject 在 UObject 類派生上配置設定的記憶體也可以通過調用 UObject: : ConditionalBeginDestroy ()成員函數手動取消配置設定(在引用計數降到0之前)。

操作方法前面也有說過,直接調用即可:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

Conditionalbegindestroy ()指令開始執行記憶體釋放過程,調用 BeginDestroy ()和 FinishDestroy ()可重複的函數。

五、托管記憶體-使用智能指針(TSharePtr,TWeakPtr,TAutoPtr)來跟蹤一個object

我們如果擔心忘記所建立的标準 c++ 對象的删除調用,可以使用智能指針來防止記憶體洩漏。 Tsharedptr 是一個非常有用的 c + + 類,它可以讓任何定制的 c + + 對象引用被計數(除了已經被引用計數的 UObject 導數)。 還提供了一個替代類 TWeakPtr,用于指向一個引用計數對象,該對象具有無法阻止删除的奇怪屬性(是以稱為弱屬性) :

但是UObject以及他的派生類,也就是任何使用NewObject或ConstructObject建立的類,不能使用TSharePtr.

如果不想使用原始指針并手動跟蹤删除不使用 UObject 派生的 c + + 代碼,那麼這段代碼是使用智能指針的好選擇,比如 TSharedPtr,TSharedRef 等等。 當使用一個動态配置設定的對象(使用 new 關鍵字建立)時,可以将其包裝在一個引用計數的指針中,以便自動取消配置設定。 智能指針的不同類型決定了智能指針的行為和删除調用時間。 具體如下:

TSharedPtr: 線程安全(前提是您提供了 ESPMode: : ThreadSafe 作為模闆的第二個參數)引用計數指針類型,訓示共享對象。 當沒有更多對共享對象的引用時,共享對象将被取消配置設定。

Tautotr: 一個非線程安全的共享指針。

添加代碼:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試
【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

弱指針和共享指針之間有一些差別。 當引用計數降到0時,弱指針不具備将對象儲存在記憶體中的能力。

使用弱指針(在原始指針上)的優點是,當手動删除弱指針下面的對象時(使用 ConditionalBeginDestroy ()) ,弱指針的引用變為 NULL 引用。 可以通過檢查以下格式的語句來檢查指針下面的資源是否仍然正确配置設定:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

共享指針是線程安全的。 這意味着可以在單獨的線程上安全地操作底層對象。

需要注意的是:不能将 TSharedRef 與 UObjects 或 UObject 導數一起使用; 隻能在自定義的 c + + 類中使用它們。 Fstructures 可以使用 TSharedPtr、 TSharedRef 和 TWeakPtr 類封裝原始指針。

如果要使用智能指針指向對象,則必須使用 TWeakObjectPointer 或 UPROPERTY ()。

如果不需要 TSharedPtr 的線程安全保證,則可以使用 tautotr。 當對某個對象的引用數目減少到0時,該對象将自動删除。

六、使用TScopedPoint 來跟蹤一個object

作用域指針是在其聲明的塊的末尾自動删除的指針。 回想一下,作用域隻是代碼中變量處于活動狀态的一部分。 範圍将持續到出現第一個結束大括号}。

例如,在下面的塊中,我們有兩個作用域。 外部作用域聲明一個整型變量 x (對于整個外部塊有效) ,而内部作用域聲明一個整型變量 y (對于内部塊有效,在聲明它的行之後) :

{
    int x;
    {
        int y;
    } // scope of y ends
} // scope of x ends
           

當引用計數對象(有超出作用域的危險)在使用期間被保留時,作用域指針非常有用。

要聲明一個作用域指針,我們可以使用以下文法:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

由于版本更新,目前的最新API已經改為TUniquePtr。舊版的文法是藍色框中的注釋代碼。

Tscopedpointer 變量類型自動向指向的變量添加引用計數。 這可以防止至少在作用域指針的生命周期内取消基礎對象的配置設定。

七、UE4的垃圾回收,以及UPROPERTU()

當有一個對象(比如 TArray)作為 UCLASS ()的 UPROPERTY ()成員時,需要将該成員聲明為 UPROPERTY ()(即使不會在 Blueprints 中編輯它) ; 否則,TArray 将無法正确配置設定。

添加代碼

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

必須将 TArray 成員列為 UPROPERTY () ,才能正确地對其進行引用計數。 如果不這樣做,則會在代碼中出現意外的記憶體錯誤類型錯誤。

Uproperty ()聲明告訴 UE4 TArray 必須受到正确的記憶體管理。 如果沒有 UPROPERTY ()聲明,TArray 将無法正常工作。

另外,當記憶體滿了,并且希望釋放其中的一些記憶體時,可以強制進行垃圾收集。 很少需要這樣做,但是可以在具有非常大的紋理(或一組紋理)的情況下這樣做,這些紋理是引用計數的,并且需要清除。

隻需對所有需要從記憶體中取消配置設定的 UObjects 調用 ConditionalBeginDestroy () ,或者将它們的引用計數設定為0。

強制垃圾回收可使用以下處理方式:

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

版本:在UE4.18 版本之後調用方式使用第二個。

八、斷點與單步執行代碼

操作步驟:

  • 将模式設定為DebugGame
  • 選擇代碼按下F9設定斷點
  • 運作程式
  • 擊中斷點時按F10繼續下一個斷點如此重複
  • 如果擊中斷點時按下F11則會基于斷點處不斷單步執行,每按一次執行一步

九、查找錯誤和使用調用堆棧

當我們按下F5使用調試模式開啟編輯器時,如過運作過程中遇到錯誤,就會出現斷點,這是一個查找錯誤的方法

調用堆棧是已執行的函數調用的清單。 當 bug 發生時,發生 bug 的那一行将被列在 callstack 的頂部

【UE4_C++】 &lt;3&gt; 記憶體管理,智能指針以及調試

另外我們可以借助C + + 分析器來進行性能的檢測。

C + + 分析器對于查找需要大量處理時間的代碼段非常有用。 使用分析器可以幫助我們找到優化過程中需要關注的代碼部分。 如果懷疑某個區域的代碼運作緩慢,那麼如果它沒有在分析器中突出顯示,那麼實際上可以确認它并不緩慢。

C++分析器的使用方法可參考以下微軟官方文檔:

https://docs.microsoft.com/en-us/visualstudio/profiling/?view=vs-2017

繼續閱讀