翻译《有关编程、重构及其他的终极问题?》——7.不要在循环中调用alloca()函数

翻译《有关编程、重构及其他的终极问题?》——7.不要在循环中调用alloca()函数

标签(空格分隔): 翻译 技术 C/C++
作者:Andrey Karpov
翻译者:顾笑群 - Rafael Gu
校验者:高国栋
最后更新:2016年12月07日


本书背景说明、总目录等介绍,可以跳转到以下链接进行查看:
http://blog.youkuaiyun.com/headman/article/details/53045891

欢迎大家转载,但请附上原作者以及翻译者的名字、原文出处,以尊重光荣的劳动者。


7.不要在循环中调用alloca()函数

这次这个bug是在Pixie项目中发现的。PVS-Studio检测到的错误是:V505 The ‘alloca’ function is used inside the loop. This can quickly overflow stack(译者注:大意是在循环中发现了alloca函数,这很可能会快速让栈溢出)。

inline void triangulatePolygon(....) {
    ...
    for (i = 1; i < nloops; i++) {
        ...
        do {
            ...
            do {
                ...
                CTriVertex *snVertex =
                    (CTriVertex *) alloca(2*sizeof(CTriVertex));
                ...
            } while(dVertex != loops[0]);
            ...
        } while(sVertex != loops[i]);
        ...
    }
    ...
}

解释
函数alloca(size_t)在栈中分配内存,所以其分配的内存会在离开函数后被释放(译者注:离开函数后,栈会被自动释放)。

一般来说程序的栈内存不会太大。在使用Visual C++创建项目时,默认的栈内存大小为1M,这就是为什么如果在循环中分配栈内存的话会导致栈快速溢出的原因(译者注:其实栈内存是给线程分配的,每个线程都有自己的栈内存,当前流行的64位Linux系统一般默认线程栈内存为8M,可以通过改系统参数或者系统API方式修改默认栈内存大小)。

在上面这个例子中,循环有3次嵌套,这种数量级的调用就很容易产生栈溢出。

另外,在循环中使用类似A2W宏(ATL and MFC String Conversion Macros)也不安全,因为其中也有对alloca()函数的调用。

之前说过,Windows程序默认使用1M栈内存,这个值是可以改变的:比如可以改变项目设置中的“Stack Reserve Size”和“Stack Commit Size”项,或者直接设置编译命令行的”/STACK(Stack Allocations)”属性。然后,我们应该清楚的认识到,增加栈内存的默认大小只是延迟了栈溢出的时间,并不能解决这个问题。

建议
不要在循环中调用alloca()函数。如果你需要在循环中使用零时内存,请使用下面三种方法:
1. 在进入循环前分配好内存,然后在循环中的利用这片内存。如果你每次需要不同尺寸的内存,那么久提前分配一个最大尺寸的。如果你无法知道分配多大的内存,请看方法2。
2. 在循环的范围内直接使用栈变量,这样每次进入这个范围,这个变量都会在栈中被分配;每次离开这个范围,这个变量都会被释放。如果这个方法也不行,那就只能看方法3了。
3. 使用malloc()函数或者new操作符代替alloca()函数,又或者使用std:vector,当然这样就会造成内存的分配占用更长的时间。如果你使用了malloc()函数或者new操作符,不要忘了手动释放相关内存。另外,如果需要分配很大尺寸的内存,这种方式也不会造成内存溢出(这种方式是在堆上分配内存)。

<think> 我们面对的问题是:通过反射调用一个函数,该函数的参数类型是TMap<FString, AActor*>,我们需要填充这个参数。 在Unreal Engine中,反射系统允许我们在运行时动态调用函数。但是,对于TMap这种复杂类型,填充参数需要一些特殊的操作。 步骤: 1. 获取函数信息:我们需要通过UClass或UFunction来获取函数的参数信息。 2. 创建参数结构:函数的参数通常通过一个结构体来传递(FStruct),我们需要为这个参数结构分配内存并初始化。 3. 对于TMap类型的参数,我们需要创建一个TMap对象,并将其填充到参数结构的相应位置。 4. 由于参数是TMap<FString, AActor*>,我们需要注意: - FString是UE的标准字符串,可以直接使用。 - AActor*是指针,我们需要确保这些指针是有效的,并且可以被反射系统识别(通常通过UObject*来传递)。 具体做法: 假设我们有一个函数: void MyFunction(TMap<FString, AActor*> ActorMap); 通过反射调用这个函数,我们需要: 1. 获取UFunction对象。 2. 创建一个足够大的内存块来存放参数(通常使用FStruct来管理,但这里我们可以直接创建一个TMap对象,因为参数只有一个)。 实际上,对于非静态成员函数,参数结构可能包含this指针和其他参数。但对于只有一个参数的情况,我们可以直接为该参数分配内存。 但是,更通用的方法是使用FOutParmRec或者通过UFunction构造参数栈。 根据UE的反射调用机制,我们可以使用: UFunction* Function = ...; // 获取函数对象 FFrame Stack(Object, Function, nullptr, nullptr, Function->ChildProperties); // 构造一个栈帧 // 然后设置参数 然而,更常用的方法是使用ProcessEvent(对于UObject实例)或者使用Function->Invoke。 这里我们以ProcessEvent为例(注意:ProcessEvent通常用于UObject的实例,并且只能调用标记为UFUNCTION的函数): void UMyObject::CallFunctionWithMap() { // 假设我们有一个UFunction的指针 UFunction* Function = ...; // 创建参数内存 uint8* Params = (uint8*)FMemory_Alloca(Function->ParmsSize); // 在栈上分配 FMemory::Memzero(Params, Function->ParmsSize); // 我们需要填充参数 // 首先找到参数属性(这里假设只有一个参数) FProperty* ParamProp = Function->FirstPropertyToInit; // 注意:实际中需要遍历参数 // 但是更直接的方式是知道参数的名字,然后通过FindPropertyByName来获取 FMapProperty* MapProp = CastField<FMapProperty>(Function->FindPropertyByName(TEXT("ActorMap"))); if (MapProp) { // 获取参数内存中的偏移 void* MapAddr = MapProp->ContainerPtrToValuePtr<void>(Params); // 创建一个TMap<FString, AActor*>对象,并填充数据 TMap<FString, AActor*> MyMap; // 填充一些数据到MyMap... // 现在需要将MyMap复制到参数内存中 // 由于TMap是值类型,我们可以直接复制,但是要通过MapProp的CopyCompleteValue函数 MapProp->CopyCompleteValue(MapAddr, &MyMap); } // 调用函数 Object->ProcessEvent(Function, Params); // 清理参数(如果有析构需求) Function->DestroyParms(Params); } 但是,上述方法中,我们直接创建了一个TMap对象,然后复制到参数内存中。然而,我们如何动态地填充这个TMap呢? 如果我们不知道具体要填充哪些键值对,而是需要从外部数据源(如蓝图)动态获取,那么我们需要动态构建这个TMap。 动态构建TMap的关键: 1. 创建一个TMap<FString, AActor*>的实例。 2. 向其中添加键值对。 但是,在反射调用时,我们可能只有键和值的字符串表示(对于键)和对象路径(对于值)?所以我们需要转换。 步骤: 1. 创建空的TMap。 2. 对于每一个键值对: - 键:FString,可以直接使用。 - 值:AActor*,我们可以通过路径字符串加载,或者在场景中查找,或者通过其他方式获取。 例如,假设我们有一组字符串键和对象路径(或者对象指针),我们可以这样: TMap<FString, AActor*> MyMap; // 假设我们有一个字符串数组表示键,一个对象路径数组表示值 for (int i=0; i<Keys.Num(); i++) { // 假设我们有一个方法将对象路径转换为AActor* FSoftObjectPath Path = ValuePaths[i]; AActor* Actor = Cast<AActor>(Path.ResolveObject()); // 解析对象 if (Actor) { MyMap.Add(Keys[i], Actor); } } 然后,将MyMap通过MapProp->CopyCompleteValue复制到参数内存中。 但是,如果我们是完全通过反射来构建这个TMap(即我们不知道具体类型,只知道是FMapProperty),则需要通过MapProperty的KeyProp和ValueProp来设置每个元素。 另一种方法是使用FScriptMapHelper(类似于TArray的FScriptArrayHelper)。 使用FScriptMapHelper动态构建TMap: FScriptMapHelper MapHelper(MapProp, MapAddr); // MapAddr是参数中TMap的地址 // 首先清空(确保为空) MapHelper.EmptyValues(); // 然后添加元素 for (每个键值对) { // 添加一个新的索引 int32 NewIndex = MapHelper.AddDefaultValue_Invalid_NeedsRehash(); // 获取键和值的内存位置 uint8* KeyData = MapHelper.GetKeyPtr(NewIndex); uint8* ValueData = MapHelper.GetValuePtr(NewIndex); // 设置键 FProperty* KeyProp = MapHelper.MapLayout.KeyProp; // 假设我们有一个FString的键,我们可以这样设置: // 对于FStringProperty(注意:TMap的键是FString,所以KeyProp应该是FStrProperty) if (FStrProperty* KeyStrProp = CastField<FStrProperty>(KeyProp)) { KeyStrProp->SetPropertyValue(KeyData, TEXT("MyKey")); // 设置键 } // 设置值:值类型是AActor*,在UE中,对象指针通常用FObjectProperty或FWeakObjectProperty等 if (FObjectProperty* ValueObjProp = CastField<FObjectProperty>(MapHelper.MapLayout.ValueProp)) { // 假设我们有一个UObject*对象(注意AActor*也是UObject*) UObject* Obj = ...; // 获取到这个对象 ValueObjProp->SetObjectPropertyValue(ValueData, Obj); } // 注意:添加过程中可能需要重新哈希,所以最后需要重新哈希 } MapHelper.Rehash(); 但是,如果我们已经有一个TMap<FString, AActor*>对象,那么第一种方法(CopyCompleteValue)更简单。 然而,在反射调用中,我们可能不知道具体的类型参数(比如键是FString,值是AActor*),所以使用FScriptMapHelper是更通用的方法。 总结两种方法: 方法1(直接构建具体类型的TMap并复制): TMap<FString, AActor*> MyMap; // ... 填充MyMap MapProp->CopyCompleteValue(MapAddr, &MyMap); 方法2(通过FScriptMapHelper动态填充): FScriptMapHelper MapHelper(MapProp, MapAddr); // 循环添加键值对 选择: 如果我们在C++中明确知道类型,并且可以构建具体类型的TMap,那么方法1更简单。 如果我们在一个通用的反射函数中,不知道具体的键值类型(比如一个蓝图解释器),那么方法2更合适。 注意:在方法1中,因为TMap是值类型,所以复制是深拷贝。如果参数是const引用,那么复制操作符会处理吗? 实际上,在CopyCompleteValue中,它会根据属性类型进行复制。对于TMap,它会进行深拷贝。 但是,在函数调用后,参数内存会被销毁,深拷贝的TMap也会被析构,所以不会影响原始数据。 考虑到我们的参数类型是TMap<FString, AActor*>,而不是引用,所以函数会接受一个副本。如果是引用(TMap<FString, AActor*>&),则应该使用指针传递。 然而,在UE的反射系统中,TMap参数通常以值传递,或者以const引用传递。在蓝图调用C++函数时,TMap参数在UFUNCTION中通常以const引用传递(避免复制开销),但在反射调用时,我们仍然需要构造一个TMap对象。 注意:在UFUNCTION中,TMap参数如果声明为const引用,那么在反射调用时,我们传递的实际上是一个指向TMap的指针(因为引用在底层是指针)。所以我们需要使用CopyCompleteValue来复制整个TMap吗?还是复制指针? 实际上,在反射系统中,对于引用类型的参数,属性类型会是FMapProperty,但容器本身是通过指针传递的。因此,我们需要传递一个指向TMap的指针。 如何区分值传递和引用传递? 值传递:属性类型是FMapProperty,在参数内存中存储整个TMap。 引用传递:属性类型是FMapProperty,但参数内存中存储一个TMap*(指针)。 因此,我们需要根据函数的参数声明来确定。 在UE中,我们可以通过FProperty的Flags来检查: 如果参数属性有CPF_ReferenceParm标志,那么它是引用传递(即我们需要传递指针)。 如果参数属性有CPF_OutParm和CPF_ReferenceParm,那么是引用输出参数。 因此,在填充参数时: 如果参数是值传递(非引用),则我们在参数内存中构造整个TMap(通过CopyCompleteValue将整个TMap复制进去)。 如果参数是引用传递,则我们在参数内存中放置一个指向TMap的指针(这个指针指向我们创建的TMap)。 但是,我们的函数参数是TMap<FString, AActor*>,没有使用引用符号,所以是值传递(副本)。因此,我们使用方法1或方法2在参数内存中构造一个完整的TMap副本。 结论: 我们这里选择方法1(因为知道具体类型)来填充参数: 步骤: 1. 分配参数内存(Params)并清零。 2. 获取FMapProperty属性(通过参数名)。 3. 创建一个TMap<FString, AActor*>对象并填充数据(根据需求)。 4. 使用MapProp->CopyCompleteValue将我们创建的TMap复制到参数内存的指定位置。 5. 调用函数(ProcessEvent)。 6. 销毁参数(DestroyParms)。 示例代码: // 假设我们要调用函数名为"ExecuteWithMap",参数名为"ActorMap" UFunction* Function = MyObject->FindFunction(FName("ExecuteWithMap")); if (Function) { // 分配参数内存 uint8* Params = (uint8*)FMemory_Alloca(Function->ParmsSize); FMemory::Memzero(Params, Function->ParmsSize); // 获取参数属性 FMapProperty* MapProp = CastField<FMapProperty>(Function->FindPropertyByName(TEXT("ActorMap"))); if (MapProp) { // 创建源映射 TMap<FString, AActor*> SourceMap; // 填充SourceMap(根据实际情况) SourceMap.Add(TEXT("Actor1"), Actor1); // Actor1是某个AActor* SourceMap.Add(TEXT("Actor2"), Actor2); // 获取参数在Params中的地址 void* ParamAddr = MapProp->ContainerPtrToValuePtr<void>(Params); // 复制整个映射 MapProp->CopyCompleteValue(ParamAddr, &SourceMap); } // 调用 MyObject->ProcessEvent(Function, Params); // 清理参数(如果参数中有需要析构的类型,则必须调用) // 注意:TMap在销毁时会析构其元素,但FString和指针(AActor*)都是可平凡析构的,所以这里也可以不调用DestroyParms,但为了安全,最好调用。 Function->DestroyParms(Params); } 注意:如果函数有返回值或者输出参数,我们也需要处理,但这里假设没有。 另外,如果填充的数据量很大,使用深拷贝可能会有性能问题,但TMap的复制是深拷贝,且会复制所有元素(FString会复制字符串,指针只复制指针值)。 关于AActor*指针的安全性:由于我们只是复制指针,不会影响对象生命周期,所以只要保证在函数执行期间这些Actor没有被销毁即可。 如果在填充SourceMap之后,但在函数调用期间,某个Actor被销毁了,那么函数中使用该指针就是非法的。因此,建议使用TWeakObjectPtr或者在使用前检查IsValid。 但是,在反射调用函数内部,应该由函数自己负责检查指针有效性。 最后,如果函数的参数是const引用(即函数声明为:void MyFunction(const TMap<FString, AActor*>&)),那么在反射中,参数属性仍然是FMapProperty,但会带有REF标记(CPF_ReferenceParm)。此时,我们应该传递一个指向TMap的指针,而不是整个TMap。 因此,我们需要检查属性标志: if (MapProp->HasAnyPropertyFlags(CPF_ReferenceParm) && !MapProp->HasAnyPropertyFlags(CPF_OutParm)) { // 参数是const引用(输入引用,但不是输出) // 我们需要传递一个指向TMap的指针 void* ParamAddr = MapProp->ContainerPtrToValuePtr<void>(Params); *static_cast<TMap<FString, AActor*>**>(ParamAddr) = &SourceMap; // 注意:这里SourceMap的生命周期必须保证在函数调用期间有效 } else { // 按值传递(或者输出引用),则使用CopyCompleteValue MapProp->CopyCompleteValue(ParamAddr, &SourceMap); } 但是,通常为了避免生命周期问题(因为SourceMap是局部变量),我们更倾向于按值传递(深拷贝)。所以,如果函数声明为const引用,我们也可以创建一个堆上的TMap(比如用智能指针),但这样复杂了。 另一种做法:无论参数是值传递还是引用传递,我们都创建一个局部的TMap(在堆上分配)?这并不必要。 建议:在反射调用时,我们总是构造一个完整的TMap(在栈上或堆上),然后: - 值传递:复制整个TMap到参数内存。 - 引用传递:将参数内存中的指针指向这个局部TMap。 但要注意局部TMap的生命周期:必须确保在函数调用期间有效。所以如果使用栈上的局部变量,那么函数调用结束后,引用传递的指针就失效了。因此,对于引用传递,我们必须在堆上分配TMap,并在适当的时候释放。 考虑到复杂性和安全性,通常反射调用时参数的传递都是按值进行的(蓝图调用C++函数时,引擎内部会处理),所以我们尽量使用值传递的方式(即让函数参数是值类型,而不是引用)。 如果函数的参数是引用,而我们又不想管理堆内存,我们可以这样做: // 在栈上分配一个TMap对象(局部变量) TMap<FString, AActor*> SourceMap; // 填充... void* ParamAddr = ...; if (参数是引用传递) { // 将参数内存中的指针设置为指向SourceMap *static_cast<TMap<FString, AActor*>**>(ParamAddr) = &SourceMap; } else { MapProp->CopyCompleteValue(ParamAddr, &SourceMap); } // 调用函数(在同一个函数体内,SourceMap在函数调用期间有效) 这样是安全的,因为SourceMap的生命周期覆盖了反射调用期间。 因此,我们可以这样写: uint8* Params = ...; FMapProperty* MapProp = ...; void* ParamAddr = MapProp->ContainerPtrToValuePtr<void>(Params); TMap<FString, AActor*> SourceMap; // 局部变量,在栈上 // 填充SourceMap... if (MapProp->HasAnyPropertyFlags(CPF_ReferenceParm)) { // 引用传递(包括const引用和输出引用) // 注意:引用传递时,参数内存中存储的是一个指针(TMap<...>*) *static_cast<TMap<FString, AActor*>**>(ParamAddr) = &SourceMap; } else { // 值传递:复制整个TMap MapProp->CopyCompleteValue(ParamAddr, &SourceMap); } // 调用函数 注意:如果参数同时是输出引用(CPF_OutParm),那么函数可能会修改SourceMap,我们在调用后可以读取SourceMap来获取修改后的值。 因此,我们使用局部变量SourceMap,既可以用于输入也可以用于输出。 但是,如果函数调用结束后,我们需要获取输出值(并且参数是输出引用),那么我们可以通过SourceMap获取修改后的内容。 最后,关于垃圾回收(GC): 在反射调用期间,我们使用的AActor*指针可能会被GC回收吗? 在函数调用期间,只要这些AActor对象被UObject系统引用(比如在游戏场景中),就不会被回收。但是,如果我们存储的是临时对象,需要确保它们不被回收。 UE的GC机制会在垃圾回收时检查UObject的引用情况。我们在反射调用中,函数参数(TMap)中的指针并不会作为UObject引用来阻止GC。因此,如果函数执行时间很长或者有延迟操作,可能不安全。 因此,建议在函数内部尽快使用这些指针,或者使用TStrongObjectPtr来临时增加引用计数。 不过,在同步的反射调用中(如ProcessEvent是同步的),只要在函数调用过程中没有触发GC,就是安全的。 为了确保安全,可以在调用前增加一个GC锁: FGCObjectScopeGuard ProtectGC(MyObject); // 保护MyObject,以及我们使用的Actor指针?但这里我们无法明确指定所有Actor。 更好的做法是,在填充SourceMap时,使用TArray<AActor*>来保存这些指针,然后使用FGCObjectScopeGuard保护这个数组?但遗憾的是,FGCObjectScopeGuard只能保护UObject对象本身,不能保护裸指针数组。 另一种方法:在函数调用前,增加一个FScopeGCLock,阻止GC发生: { FScopeGCLock GCGuard; // 分配参数、填充、调用函数 // ... } // GC恢复 这样,在函数执行期间不会发生GC,确保Actor指针有效。 但是,阻止GC可能会引起性能问题,所以只在必要时使用。 总结: 通过反射调用函数并填充TMap<FString, AActor*>参数的方法: 1. 获取函数和参数属性。 2. 在栈上分配参数内存。 3. 创建一个局部TMap<FString, AActor*>变量,并填充所需数据。 4. 根据参数是值传递还是引用传递,将TMap复制到参数内存或者将指针指向局部TMap。 5. 使用FScopeGCLock确保在函数调用期间不会发生GC(可选,但建议在不确定时使用)。 6. 调用ProcessEvent。 7. 如果有输出引用,可以读取局部TMap获取修改后的值。 8. 销毁参数内存(使用Function->DestroyParms)。 注意:在调用DestroyParms时,对于值传递的TMap,会调用其析构函数(释放FString和内部结构),而引用传递的TMap(指针)不会在参数内存中析构,因为指针本身是平凡的。 因此,对于引用传递,我们不需要在DestroyParms中做任何特殊处理(指针的析构是平凡的)。 示例代码: // 获取函数 UFunction* Function = ...; // 创建参数内存 uint8* Params = (uint8*)FMemory_Alloca(Function->ParmsSize); FMemory::Memzero(Params, Function->ParmsSize); // 获取参数属性 FMapProperty* MapProp = CastField<FMapProperty>(Function->FindPropertyByName(TEXT("ActorMap"))); if (!MapProp) { // 错误处理 return; } // 创建源TMap TMap<FString, AActor*> SourceMap; // 填充SourceMap(根据实际情况) // 确定参数地址 void* ParamAddr = MapProp->ContainerPtrToValuePtr<void>(Params); // 检查引用传递 if (MapProp->HasAnyPropertyFlags(CPF_ReferenceParm)) { // 引用传递:将参数内存中的指针指向SourceMap *reinterpret_cast<TMap<FString, AActor*>**>(ParamAddr) = &SourceMap; } else { // 值传递:复制整个映射 MapProp->CopyCompleteValue(ParamAddr, &SourceMap); } // 加锁防止GC(可选) FScopeGCLock GCGuard; // 调用函数 MyObject->ProcessEvent(Function, Params); // 如果有输出参数(包括引用传递并且函数内部修改了SourceMap),可以在这里读取SourceMap // 注意:如果函数有返回值,也需要处理(返回值也在Params中) // 销毁参数 Function->DestroyParms(Params); 以上就是在反射调用中填充TMap<FString, AActor*>参数的方法。 </think> ### 通过反射填充 `TMap<FString, AActor*>` 参数的完整指南 在 UE 中通过反射调用函数时,填充 `TMap<FString, AActor*>` 参数需要处理复杂类型的内存布局和对象引用问题。以下是具体步骤: --- #### 一、准备工作 1. **获取函数和参数信息**: ```cpp // 获取目标函数 UFunction* Function = TargetObject->FindFunction(FName("YourFunctionName")); // 获取映射属性 FMapProperty* MapProp = CastField<FMapProperty>(Function->FindPropertyByName(TEXT("ParamName"))); ``` - 使用 `FindPropertyByName()` 定位参数属性[^1] 2. **分配参数内存**: ```cpp uint8* Params = (uint8*)FMemory_Alloca(Function->ParmsSize); FMemory::Memzero(Params, Function->ParmsSize); ``` --- #### 二、填充映射数据(两种方法) ##### 方法1:直接构建后复制(推荐) ```cpp // 创建临时映射 TMap<FString, AActor*> TempMap; TempMap.Add("Key1", ActorPtr1); TempMap.Add("Key2", ActorPtr2); // 获取参数内存地址 void* MapAddr = MapProp->ContainerPtrToValuePtr<void>(Params); // 深拷贝到反射内存 MapProp->CopyCompleteValue(MapAddr, &TempMap); ``` - **适用场景**:已知键值对具体内容时 - **优点**:代码简洁,直接利用 UE 的深拷贝机制[^1] ##### 方法2:动态构建(通用反射) ```cpp // 初始化映射助手 FScriptMapHelper MapHelper(MapProp, MapAddr); MapHelper.EmptyValues(); // 清空现有内容 // 添加新元素 int32 NewIndex = MapHelper.AddDefaultValue_Invalid_NeedsRehash(); // 设置键(FString) uint8* KeyData = MapHelper.GetKeyPtr(NewIndex); CastField<FStrProperty>(MapHelper.GetKeyProperty())->SetPropertyValue(KeyData, TEXT("DynamicKey")); // 设置值(AActor*) uint8* ValueData = MapHelper.GetValuePtr(NewIndex); CastField<FObjectProperty>(MapHelper.GetValueProperty())->SetObjectPropertyValue(ValueData, TargetActor); // 重建哈希 MapHelper.Rehash(); ``` - **适用场景**:动态生成键值对时 - **关键点**: - 使用 `FScriptMapHelper` 管理内存布局[^1] - 键通过 `FStrProperty` 设置 - 值通过 `FObjectProperty` 设置对象指针 --- #### 三、调用函数与清理 ```cpp // 防止GC回收对象 FScopeGCLock GCGuard; // 执行函数 TargetObject->ProcessEvent(Function, Params); // 清理参数内存 Function->DestroyParms(Params); ``` - **GC保护**:确保 `AActor*` 在调用期间不被回收[^1] - **内存清理**:必须调用 `DestroyParms()` 释放容器资源 --- #### 四、关键注意事项 1. **引用传递处理**: ```cpp if (MapProp->HasAnyPropertyFlags(CPF_ReferenceParm)) { *static_cast<TMap<FString, AActor*>**>(MapAddr) = &TempMap; } ``` - 检测 `CPF_ReferenceParm` 标志处理引用参数 2. **对象有效性验证**: ```cpp if (IsValid(ActorPtr)) { // 检查对象有效性 // 添加到映射 } ``` - 避免使用已被销毁的 `AActor*` 指针 3. **异步操作安全**: - 若涉及延迟操作,应将 `AActor*` 替换为 `TWeakObjectPtr<AActor>`: ```cpp TMap<FString, TWeakObjectPtr<AActor>> SafeMap; ``` --- #### 五、错误排查 1. **类型检查**: ```cpp // 验证键值类型 FStrProperty* KeyProp = CastField<FStrProperty>(MapHelper.GetKeyProperty()); FObjectProperty* ValueProp = CastField<FObjectProperty>(MapHelper.GetValueProperty()); ``` 2. **内存诊断**: - 使用 `MapProp->GetSize()` 检查参数内存大小 - 启用 `-unattended` 模式捕获反射错误 > **重点提示**:当传递 `AActor*` 指针时,必须确保目标对象未被垃圾回收。在异步场景中优先使用 `TWeakObjectPtr` 或添加 `AddToRoot()` 保护[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值