C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

UE4/C++: TArray 夏の怪談💀 AllocatorInstance が nullptr の巻

怪異

f:id:USAGI-WRP:20180705150332p:plain

↑キャッチーな画像という事ではなくて、実際にこういう事が「うっかり」で起こるので気をつけたい、という怪談です💀

冒頭のスクリーンショットは UE4 の Array.h ( TArray )の ResizeTo メンバー関数が呼ばれた際に AllocatorInstance メンバーが nullptr のために実行時にアクセス違反が起こりアプリが死ぬ瞬間のものです。 ResizeTo は Init などから内部的に呼ばれます。

原因

この現象は TArray をメンバー変数として持つユーザー定義型の UCLASS において、 UPROPERTY を宣言に付与しない事で発生し得る実装となります。

// 怪異パターン
UCLASS()
class UMyAwesomeComponent: USceneComponent
{ GENERATED_BODY()
// ...
TArray< int32 > array_without_property_marking; // <-- OOPS!
// ...
};

「発生します」と断言できない理由は、 UE4 のガベージコレクションに起因します。 UCLASS のメンバー変数で UPROPERTY の付いたメンバー変数は UE4 のガベージコレクションの「管理対象」として「正常」に扱われます。 UPROPERTY を付けないと UE4 のガベージコレクションは UCLASS のメンバー変数としてのガベージ性(オブジェクトをゴミとして回収してよいか)を考慮しなくなり、メンバー変数を持つクラスは生きていて現役で使用中なのに、メンバー変数が UE4 のガベージコレクションに回収されて更地( nullptr )になるという事が起こり得るようになります。この UE4 の UPROPERTY によるガベージコレクションの仕組みは UPROPERTY が直接付けられたオブジェクトに留まらず、そのオブジェクトの内部にも及びます(例えば UCLASS な UMyComponent* c を別のどこかの AMyActor* a が UPROPERTY でメンバーに持つ場合、 c オブジェクトそのものだけでなく、 c がメンバー変数として持つ UPROPERTY 付きのメンバー変数群も a のガベージ性から保持・回収が判断されるようになります )。

と、言うわけで、この問題(バグ)を埋め込んでしまったクラスが、さらに動的に生成されたり、比較的短命だったり、生成直後には然程使わないが暫くしてから動き出したり消えたりするような何かだった場合、「よくわからんがたまに落ちる」というプログラミング界隈の代表的な怪談が発生し得る状況になります。

// 良好パターン
UCLASS()
class UMyAwesomeComponent: USceneComponent
{ GENERATED_BODY()
// ...
UPROPERTY() TArray< int32 > array_with_property_marking; // <-- GOOD!
// ...
};

こわい

こわいですね💀