You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 05:05:31