UE5.3中指针地址帧后变更导致取值异常的技术求助
问题描述
我在UE5.3中遇到了内存地址失效的异常问题(未测试其他引擎版本):
核心代码实现
MyClass定义:
class MyClass { private: float* Value; public: MyClass(float& InValue) : Value(&InValue) { } float GetValue() { return *Value; } };
FMyStruct定义(标记为BlueprintType):
USTRUCT(BlueprintType) struct MYMODULE_API FMyStruct { GENERATED_BODY() private: TSharedPtr<MyClass> NewClass = nullptr; UPROPERTY() float Value; public: FMyStruct() { } FMyStruct(float InValue) : Value(InValue) { NewClass = MakeShared<MyClass>(Value); } TSharedPtr<MyClass> GetPtr() { return NewClass; } };
自定义UObject中的初始化代码:
UCLASS() class MYMODULE_API UMyObject: UObject { GENERATED_BODY() public: UPROPERTY(BlueprintReadWrite) FMyStruct MyProperty; void BeginPlay() { MyProperty = FMyStruct(100.0f); float TempFloat = MyProperty.GetPtr()->GetValue(); // 此处返回正确值 BP_BeginPlay(); } UFUNCTION(BlueprintImplementableEvent) void BP_BeginPlay(); };
异常现象
初始化后立即调用MyProperty.GetPtr()->GetValue()能得到正确值,BP_BeginPlay中调用也正常,但经过一帧后(用0延迟节点跳过一帧),该调用返回错误值。调试发现MyClass::Value指向的内存地址在初始化后过一帧发生了变化。
问题原因与解决方案
问题根源
UE的UObject和UPROPERTY标记的结构体(如FMyStruct)受引擎内存管理系统(垃圾回收、序列化、内存压缩)管控:
- 当UMyObject被GC压缩、或者结构体被复制/序列化时,FMyStruct实例的内存地址会被移动,其中的
Value成员的地址也会随之改变 - MyClass中存储的是
Value的原始指针,不会自动更新,导致一帧后变成野指针,访问时返回错误值
解决方案
方案1:移除指针依赖(最简单可靠)
直接让MyClass存储float值,而非指针,彻底避免地址失效问题:
class MyClass { private: float Value; public: MyClass(float InValue) : Value(InValue) { } float GetValue() { return Value; } }; // 对应FMyStruct构造函数修改: FMyStruct(float InValue) : Value(InValue) { NewClass = MakeShared<MyClass>(Value); }
方案2:保留指针但同步地址(适合必须用指针的场景)
重载FMyStruct的赋值运算符和移动构造函数,确保结构体被复制/移动时,更新MyClass内的指针:
USTRUCT(BlueprintType) struct MYMODULE_API FMyStruct { GENERATED_BODY() private: TSharedPtr<MyClass> NewClass = nullptr; UPROPERTY() float Value; public: FMyStruct() { } FMyStruct(float InValue) : Value(InValue) { NewClass = MakeShared<MyClass>(Value); } // 重载赋值运算符 FMyStruct& operator=(const FMyStruct& Other) { if (this != &Other) { Value = Other.Value; // 重新创建MyClass,绑定当前实例的Value地址 NewClass = MakeShared<MyClass>(Value); } return *this; } // 重载移动构造 FMyStruct(FMyStruct&& Other) noexcept : Value(MoveTemp(Other.Value)), NewClass(MoveTemp(Other.NewClass)) { // 移动后更新指针到当前实例的Value if (NewClass.IsValid()) { // 需要给MyClass添加SetValue方法来更新指针 NewClass->SetValue(Value); } } TSharedPtr<MyClass> GetPtr() { return NewClass; } }; // MyClass添加SetValue方法 class MyClass { private: float* Value; public: MyClass(float& InValue) : Value(&InValue) { } float GetValue() { return *Value; } void SetValue(float& InValue) { Value = &InValue; } };
方案3:绑定到UObject生命周期(进阶)
如果MyClass需要始终关联UMyObject的生命周期,可以让MyClass持有UMyObject的TWeakObjectPtr,通过UObject间接访问Value,避免直接依赖结构体成员地址。
内容的提问来源于stack exchange,提问作者Rav Rayle
相关产品推荐
相关产品推荐

