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

C++如何正确实现不同定义结构体间的数组成员拷贝转换

跨程序结构体转换实现(Carmaker结构体A转Labview结构体B)

问题背景

需要实现结构体内部数组成员的跨结构体拷贝,完成跨程序数据适配:Carmaker侧使用结构体A类型数据,Labview侧使用结构体B类型数据,需实现结构体A到B的可靠转换。

相关定义

基础类型定义

#ifndef CONVERTERCPP2LV_ALGOBASETYPES_H
#define CONVERTERCPP2LV_ALGOBASETYPES_H

#include <cstdint>

namespace AlgoBaseTypes
{
    typedef float float32;
    typedef std::int32_t sint32;
    typedef std::uint32_t uint32;
    typedef std::int16_t sint16;
    typedef std::uint16_t uint16;
    typedef std::int8_t sint8;
    typedef std::uint8_t uint8;
    typedef std::uint64_t uint64;
    typedef std::int64_t sint64;
    typedef double float64;
    typedef std::uint8_t boolean;
#define FALSE AlgoBaseTypes::boolean {0}
#define TRUE AlgoBaseTypes::boolean {1}
}

#endif //CONVERTERCPP2LV_ALGOBASETYPES_H

结构体A(Carmaker侧)

typedef struct ts_InnovizContextData
{
    // firmware version as string
    ::AlgoBaseTypes::uint8 a_FirmwareVersion[20]{ 0 };
    // Indicates if the Data is integral or corrupted
    te_DataValidity E_DataValidity{ te_DataValidity::E_DataInvalid };
    ::AlgoBaseTypes::uint64 FrameCounter{ 0 };
    ::AlgoBaseTypes::uint64 TimestampMeasureStart{ 0 };
    // unique sensor ID
    ::AlgoBaseTypes::uint32 SensorId{ 0 };
    // The meaning of values of LidarState is defined in the following document (Chapter 6.1) 
    ::AlgoBaseTypes::uint8 LidarState{ 0 };     
    ::AlgoBaseTypes::uint8 LidarSubState{ 0 };
    // number of all Lidar detections in each LRF
    ::AlgoBaseTypes::uint32 a_NumDetections[4]{ 0 };
} ts_InnovizContextData;

typedef struct ts_InnovizDetectionData
{
    ts_InnovizContextData s_ContextData;
} ts_InnovizDetectionData;

结构体B(Labview侧)

typedef struct {
    int32_t dimSize;
    uint8_t* a_FirmwareVersion;
    } TD5;
typedef TD5 **TD5Hdl;

// 按TD5规则补全缺失的TD4定义(对应uint32_t类型数组)
typedef struct {
    int32_t dimSize;
    uint32_t* elt;
    } TD4;
typedef TD4 **TD4Hdl;

typedef struct {
    uint64_t FrameCounter;
    uint64_t TimestampMeasureStart;
    uint32_t SensorId;
    uint8_t LidarState;
    uint8_t LidarSubState;
    TD4Hdl a_NumDetections;
    TD5Hdl a_FirmwareVersion;
    uint16_t te_DataValidity;
    } TD3;

typedef struct {
    //TD2 ts_InterfaceVersion;
    TD3 ts_InnovizContextData;
    //TD6 ts_Detection;
    } TD1;

原有写法的错误点

  • 整体直接memcpy两个结构体完全不可行:两个结构体成员顺序、内存布局完全不一致,且结构体B的数组成员是未分配内存的二级指针(Labview句柄),直接拷贝会把源结构体的数组内容覆写到指针变量本身,触发野指针、空引用崩溃。
  • 硬编码拷贝22字节的逻辑不可靠:不同编译器、不同结构体对齐选项下成员偏移不固定,且结构体B的成员顺序和A完全错位,硬编码长度只能在极特定的编译配置下偶然拷贝对开头部分数据,数组、后续成员必然错乱。
  • 直接给Labview数组指针赋值栈上临时数组的写法错误:栈内存在函数退出后自动回收,Labview侧后续访问会触发野指针,且不符合Labview句柄的内存管理规则。

正确实现方案

Labview生成的C接口句柄(*Hdl类型)必须使用Labview提供的官方内存管理函数(DSNewHandle/DSDisposeHandle,部分版本用NIRTDSPMemoryAllocate)分配释放,不能直接用malloc/new,否则Labview侧释放内存时会触发崩溃。
转换逻辑遵循「普通成员逐个赋值、数组成员先分配内存再逐段拷贝」的原则,完全规避结构体对齐、成员顺序差异带来的问题,参考实现如下:

#include <cstring>
// 需提前引入Labview C接口头文件,提供DSNewHandle、DSDisposeHandle等API

// 转换函数:传入源结构体A指针,填充目标结构体B
// 注意:使用完成后需调用Labview内存接口释放两个数组句柄,避免内存泄漏
int ConvertA2B(const ts_InnovizDetectionData* src, TD1* dst)
{
    if (!src || !dst) return -1;
    const ts_InnovizContextData* srcCtx = &src->s_ContextData;
    TD3* dstCtx = &dst->ts_InnovizContextData;

    // 第一步:拷贝所有非数组普通成员,逐个赋值不受对齐、顺序影响
    dstCtx->FrameCounter = srcCtx->FrameCounter;
    dstCtx->TimestampMeasureStart = srcCtx->TimestampMeasureStart;
    dstCtx->SensorId = srcCtx->SensorId;
    dstCtx->LidarState = srcCtx->LidarState;
    dstCtx->LidarSubState = srcCtx->LidarSubState;
    dstCtx->te_DataValidity = static_cast<uint16_t>(srcCtx->E_DataValidity); // 确认枚举值范围不超过uint16上限,避免截断

    // 第二步:处理a_NumDetections数组(固定长度4,元素为uint32)
    const size_t numDetectLen = sizeof(srcCtx->a_NumDetections) / sizeof(srcCtx->a_NumDetections[0]);
    TD4Hdl numDetectHdl = static_cast<TD4Hdl>(DSNewHandle(sizeof(int32_t) + numDetectLen * sizeof(uint32_t)));
    if (!numDetectHdl) return -2; // 内存分配失败
    (*numDetectHdl)->dimSize = static_cast<int32_t>(numDetectLen);
    memcpy((*numDetectHdl)->elt, srcCtx->a_NumDetections, numDetectLen * sizeof(uint32_t));
    dstCtx->a_NumDetections = numDetectHdl;

    // 第三步:处理a_FirmwareVersion数组(固定长度20,元素为uint8)
    const size_t fwVerLen = sizeof(srcCtx->a_FirmwareVersion) / sizeof(srcCtx->a_FirmwareVersion[0]);
    TD5Hdl fwVerHdl = static_cast<TD5Hdl>(DSNewHandle(sizeof(int32_t) + fwVerLen * sizeof(uint8_t)));
    if (!fwVerHdl)
    {
        DSDisposeHandle(numDetectHdl); // 释放之前分配的内存,避免泄漏
        return -3;
    }
    (*fwVerHdl)->dimSize = static_cast<int32_t>(fwVerLen);
    memcpy((*fwVerHdl)->a_FirmwareVersion, srcCtx->a_FirmwareVersion, fwVerLen * sizeof(uint8_t));
    dstCtx->a_FirmwareVersion = fwVerHdl;

    return 0;
}

注意事项

  • 禁止依赖硬编码偏移、长度做结构体拷贝:只要编译选项、编译器、结构体定义稍有调整,这类写法就会出现数据错乱、内存越界问题。
  • 枚举类型转换时做好值范围校验:te_DataValidity的枚举取值不要超过uint16_t的最大值,避免赋值时出现数据截断。
  • 内存管理要配对:DSNewHandle分配的句柄,在数据使用完成后必须调用DSDisposeHandle释放,不要混用C/C++原生内存接口和Labview内存接口。

内容的提问来源于stack exchange,提问作者RoDae

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 12:39:22