How do you debug a crash from an invalid UObject reference?
This tracker crashes a few seconds after an enemy dies: UpdateScore dereferences a nullptr or garbage address. The check if (LastKilledEnemy) does not help — the pointer is non-null yet the memory is freed.
// 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(); // crash
}Find and fix the bug.
Read the callstack to find the dereferenced pointer and check whether it is a UPROPERTY(). A non-reflected pointer dangling after GC is the usual cause: the collector neither keeps the object alive nor nulls the pointer, so if (ptr) still passes on freed memory. Fix it by adding UPROPERTY() or using TWeakObjectPtr with an IsValid guard.
- ✗Removing
UPROPERTY()to 'stop the GC nulling' — that causes the dangle - ✗Assuming the crash is build-config specific rather than a real lifetime bug
- ✗Forcing
CollectGarbage()instead of fixing the missing reference
- →How would
gc.PendingKillEnabledor stomp allocator help isolate the crash? - →Why does adding
UPROPERTY()change a crash into a safe null dereference you can guard?
Symptom
The game crashes at random moments, often a few seconds after an enemy is destroyed. The callstack points at a nullptr or garbage-address dereference inside a method.
Buggy code
// AScoreTracker.h
UCLASS()
class AScoreTracker : public AActor
{
GENERATED_BODY()
public:
void TrackEnemy(AEnemy* Enemy);
void UpdateScore();
private:
// BUG: raw pointer with no UPROPERTY()
AEnemy* LastKilledEnemy = nullptr;
};
// AScoreTracker.cpp
void AScoreTracker::TrackEnemy(AEnemy* Enemy)
{
LastKilledEnemy = Enemy; // stored a raw pointer
}
void AScoreTracker::UpdateScore()
{
// A few frames later the enemy is destroyed and collected.
// LastKilledEnemy now dangles — the GC never nulled it.
int32 Bonus = LastKilledEnemy->GetScoreValue(); // CRASH
}Diagnosis
Because LastKilledEnemy is not marked UPROPERTY(), the garbage collector cannot see this reference. When the enemy becomes unreachable and is collected, the GC does not null the pointer — it dangles. An if (LastKilledEnemy) check is useless: the pointer is non-null but the memory is already freed. The crash is delayed, so the callstack points at UpdateScore, not at the real cause.
Fix
private:
// Option A: strong reference — UPROPERTY() keeps the enemy alive
// and nulls the pointer when the enemy is destroyed.
UPROPERTY()
AEnemy* LastKilledEnemy = nullptr;
// Option B: weak reference — does not keep the enemy alive,
// but reports invalidity safely.
TWeakObjectPtr<AEnemy> LastKilledEnemyWeak;void AScoreTracker::UpdateScore()
{
// With option A: the check now actually works.
if (IsValid(LastKilledEnemy))
{
int32 Bonus = LastKilledEnemy->GetScoreValue();
}
// With option B:
if (AEnemy* Enemy = LastKilledEnemyWeak.Get())
{
int32 Bonus = Enemy->GetScoreValue();
}
}The choice depends on intent: if the tracker should logically extend the enemy's lifetime, use option A; if it merely observes and the enemy may vanish at any time, use option B.