You need to enable JavaScript to run this app. C结构体转uint8_t字节用于USB传输的实现及全量memcpy可行性
优惠活动
大模型
产品
解决方案
定价
更多

C结构体转uint8_t字节用于USB传输的实现及全量memcpy可行性

结构体通过USB报文发送的标准实现说明

先纠正你现有写法的隐蔽语法问题

  • 你代码里写的memcpy(ptr, T.Results, sizeof(float));存在硬错误:T.Results是float类型的数值,不是内存地址,memcpy需要传入源数据的指针,正确写法应该是传&T.Results。这类把普通值当指针传的写法,编译时可能不会直接报错,但运行时会访问非法内存,直接导致MCU跑飞。
  • 你设想的整体拷贝写法memcpy(ptr, T, sizeof(test_t));也有两处问题:一是T是结构体实例,需要加取地址符写成&T才能作为memcpy的源地址;二是结构体类型名和你定义的Test_t大小写不匹配,修正这两处才是可以正常运行的整体拷贝写法。

直接整体拷贝结构体的可行性与风险

整体memcpy整个结构体的写法仅在非常严苛的固定场景下可用,否则会出现难以排查的通信错误:

  • 结构体对齐填充问题:C语言为了提升内存访问效率,会在结构体成员之间插入无意义的填充字节,这些填充字节的值是随机的,直接拷贝会把无效数据也打包进USB报文,不仅浪费带宽,接收端按约定长度解析时还会出现数据错位。
  • 端序不匹配问题:不同硬件平台的多字节数据(float、int)的存储字节序可能不一样(比如多数STM32内核是小端,部分专用通信外设是大端),如果收发两端端序不一致,直接拷贝的多字节值到对端解析会完全失真。

嵌入式通信场景的两种标准实现方案

方案1:固定硬件场景的快速实现

如果收发两端的硬件架构、编译规则完全固定,不会有跨设备兼容的需求,可以给结构体加上打包(packed)修饰,强制取消结构体的对齐填充字节:

  • GCC/CLANG编译环境写法:typedef struct __attribute__((packed)) { float Results; int settings; } Test_t;
  • Keil MDK编译环境写法:__packed typedef struct { float Results; int settings; } Test_t;
    这种写法下结构体内部没有任何填充字节,配合统一的端序转换逻辑(比如把多字节值转成报文约定的字节序),就可以直接用整体memcpy的方式打包数据,代码量最少。

方案2:跨设备兼容的通用工业级做法

绝大多数工业通信项目不会依赖整体拷贝的方式,而是采用逐字段序列化的逻辑,也就是你现有思路的优化版:
按照USB报文预先约定的字段顺序、字节长度、端序规则,依次把每个成员的值拷贝到输出缓存中。这种写法完全不受结构体对齐、编译器规则、硬件架构的影响,兼容性最强。优化后的示例代码:

int8_t GetTestData(uint8_t *outputBytes, uint16_t max_buf_len)
{
    // 先校验输出缓存长度是否足够承载整个报文
    const uint16_t total_len = sizeof(float) + sizeof(int);
    if (max_buf_len < total_len) {
        return -1; // 返回缓存长度不足的错误码
    }

    uint8_t *ptr = outputBytes;
    // 依次拷贝每个字段,可按需插入端序转换逻辑
    memcpy(ptr, &T.Results, sizeof(float));
    ptr += sizeof(float); // 指针手动移动到下一个字段的写入位置
    memcpy(ptr, &T.settings, sizeof(int));

    return 0; // 返回打包成功的状态码
}

这种写法看起来比整体拷贝繁琐,但完全避免了对齐、端序这类隐蔽的兼容问题,是USB、CAN、Modbus等通信协议实现时的通用选择。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 08:27:04