Как отлаживать падение из-за невалидной ссылки на UObject?
Этот трекер падает через несколько секунд после смерти врага: UpdateScore разыменовывает nullptr или мусорный адрес. Проверка if (LastKilledEnemy) не помогает — указатель ненулевой, но память уже освобождена.
// AScoreTracker.h
UCLASS()
class AScoreTracker : public AActor
{
GENERATED_BODY()
public:
void TrackEnemy(AEnemy* Enemy);
void UpdateScore();
private:
AEnemy* LastKilledEnemy = nullptr;
};
// AScoreTracker.cpp
void AScoreTracker::TrackEnemy(AEnemy* Enemy)
{
LastKilledEnemy = Enemy;
}
void AScoreTracker::UpdateScore()
{
int32 Bonus = LastKilledEnemy->GetScoreValue(); // падение
}
Найдите и исправьте ошибку.
Прочитайте стек вызовов, найдите разыменованный указатель и проверьте, является ли он UPROPERTY(). Обычная причина — неотражённый указатель, повисший после GC; решение — добавить UPROPERTY() или применить TWeakObjectPtr с проверкой IsValid.
- ✗Убирать
UPROPERTY(), чтобы «GC не обнулял», — именно это и вызывает висячесть - ✗Считать, что падение зависит от конфигурации сборки, а не настоящий баг времени жизни
- ✗Принудительно вызывать
CollectGarbage()вместо исправления пропущенной ссылки
- →Как
gc.PendingKillEnabledили stomp-аллокатор помогли бы локализовать падение? - →Почему добавление
UPROPERTY()превращает падение в безопасное разыменование null, которое можно защитить?
Симптом
Игра падает в случайные моменты, часто через несколько секунд после уничтожения врага. Стек вызовов указывает на разыменование nullptr или мусорного адреса внутри метода.
Код с багом
// AScoreTracker.h
UCLASS()
class AScoreTracker : public AActor
{
GENERATED_BODY()
public:
void TrackEnemy(AEnemy* Enemy);
void UpdateScore();
private:
// БАГ: сырой указатель без UPROPERTY()
AEnemy* LastKilledEnemy = nullptr;
};
// AScoreTracker.cpp
void AScoreTracker::TrackEnemy(AEnemy* Enemy)
{
LastKilledEnemy = Enemy; // сохранили сырой указатель
}
void AScoreTracker::UpdateScore()
{
// Через несколько кадров враг уничтожен и собран GC.
// LastKilledEnemy теперь висячий — GC не обнулил его.
int32 Bonus = LastKilledEnemy->GetScoreValue(); // ПАДЕНИЕ
}
Диагностика
Поскольку LastKilledEnemy не помечен UPROPERTY(), сборщик мусора не видит эту ссылку. Когда враг становится недостижимым и собирается, GC не обнуляет указатель — он остаётся висячим. Проверка if (LastKilledEnemy) бесполезна: указатель ненулевой, но память уже освобождена. Падение отложено, поэтому стек вызовов указывает на UpdateScore, а не на настоящую причину.
Исправление
private:
// Вариант A: сильная ссылка — UPROPERTY() удерживает врага живым
// и обнуляет указатель, когда враг уничтожен.
UPROPERTY()
AEnemy* LastKilledEnemy = nullptr;
// Вариант B: слабая ссылка — не удерживает врага живым,
// но безопасно сообщает о невалидности.
TWeakObjectPtr<AEnemy> LastKilledEnemyWeak;
void AScoreTracker::UpdateScore()
{
// С вариантом A: проверка теперь действительно работает.
if (IsValid(LastKilledEnemy))
{
int32 Bonus = LastKilledEnemy->GetScoreValue();
}
// С вариантом B:
if (AEnemy* Enemy = LastKilledEnemyWeak.Get())
{
int32 Bonus = Enemy->GetScoreValue();
}
}
Выбор зависит от намерения: если трекер логически должен продлевать жизнь врага — вариант A; если он лишь наблюдает и враг может исчезнуть в любой момент — вариант B.