UE4/C++: TArray 夏の怪談💀 AllocatorInstance が nullptr の巻
怪異
↑キャッチーな画像という事ではなくて、実際にこういう事が「うっかり」で起こるので気をつけたい、という怪談です💀
冒頭のスクリーンショットは 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! // ... };
こわい
UE4のTArray怖い話きた。今夏最高に冷える怪談の様子を写真に収めることに成功しました💀 pic.twitter.com/nyQWQNInDk
— デス味(デス・ウェイ)うさぎ // Usagi Ito (@USAGI_WRP) July 5, 2018
TArray オブジェクトそのものは生きているのでまさに怪談💀 原因はお察し、 UPROPERTY と GC というそれだけのこと、というところもありがちな怪談の落ちっぽくて怪談💀
— デス味(デス・ウェイ)うさぎ // Usagi Ito (@USAGI_WRP) July 5, 2018
これがまた NewObject される子で、たいてい短命だけどたまに休眠しつつ長生きして爆発する子で・・・((((;゚Д゚))))ガクガクブルブル
— デス味(デス・ウェイ)うさぎ // Usagi Ito (@USAGI_WRP) July 5, 2018
こわいですね💀