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

含uint16_t与双uint8_t的Union结构体在ARM架构下为何出现额外字节?

内存对齐导致结构体出现额外字节的问题分析

问题描述

我定义了包含各类结构体与变量的内存结构,其中CRCr为联合(Union)结构体,包含uint16_t类型的CRC以及由两个uint8_t(D7、D8)组成的GEN_CRCD_int结构体。后续含位域和单字节Flags的结构体表现正常,但通过指针遍历整个结构体时,出现了额外字节(日志中序列为05 06 00 07 08,00为额外字节)。我猜测由于32位架构的内存对齐要求,CRCr前被填充了字节,因为此前的结构体部分结束在第7字节。该代码在8位AVR架构下运行正常,但在树莓派PICO(ARM 32位)上出现此问题,请问该情况是否合理?

相关代码

结构体定义

struct GEN_CRCD_int
{
    volatile uint8_t D7;
    volatile uint8_t D8;
};

struct GENet_header
{
    volatile uint16_t dstAddr;
    volatile uint16_t srcAddr;
    volatile uint8_t D0;
    volatile uint8_t D1;
    volatile uint8_t D2;
    volatile uint8_t D3;
    volatile uint8_t D4;
    volatile uint8_t D5;
    volatile uint8_t D6;

    volatile union
    {
        volatile struct GEN_CRCD_int D;
        volatile uint16_t CRC;
    } CRCr;
    volatile uint8_t L;

    volatile union
    {
        volatile uint8_t F;
        volatile struct GENet_flags flags;
    } Flags;
    volatile uint8_t PN;
    volatile uint8_t CH;                           // CRC8 header
};

调试代码

frame->header.D1=1;
frame->header.D2=2;
frame->header.D3=3;
frame->header.D4=4;
frame->header.D5=5;
frame->header.D6=6;
frame->header.CRCr.D.D7=7;
frame->header.CRCr.D.D8=8;
frame->header.L=9;
frame->header.locked = 1;
uint16_t avail = GEN_send_available(usart); // we assume uart buffer is 8 bit size ! :)
if (avail > 8)                              // if we have at least 8 bytes in buffer
{
    uint16_t frame_size = GEN_get_frame_size(frame);
    char *bpointer = (char *)&frame->buffer;
    uint8_t limit = avail;
    // we need to reserve at least 2 bytes more to compensate for pre-header flush and sync bytes
    if (frame_size - frame->header.frame_index + 2 <= avail)
    {
        limit = frame_size - frame->header.frame_index + 2;
    }
    else
    {
        limit = avail;
    }
    
    GEN_transmit_ar(1,"SENDING [",9);// debug
    uint8_t i = 0;
    char *pointer;
    if (frame->header.frame_index == 0)
    {
        GEN_transmit(usart, 0xFF); // flush byte - helps unclog bad data in usarts
        limit--;
        GEN_transmit(usart, 0b10101010); // sync byte
        limit--;
    }
    for (i = 0; i < limit; i++)
    {
        if (frame->header.frame_index < 17)
        {
            pointer = (char *)frame + frame->header.frame_index;
            GEN_transmit(usart, *pointer);
            byteToHex(*pointer  & 0xFF,tchar);// debug
            GEN_transmit_ar(1,tchar,2);// debug
            GEN_transmit(1,' ');// debug
        }
        else
        {
            pointer = (char *)bpointer + frame->header.frame_index - 17;
            GEN_transmit(usart, *pointer);
            byteToHex(*pointer  & 0xFF,tchar);// debug
            GEN_transmit_ar(1,tchar,2);// debug
            GEN_transmit(1,' ');// debug
        }
        frame->header.frame_index++;
    }
    if (frame->header.frame_index == frame_size)
    {
        frame->header.is_sent = 1;
        frame->header.sending = 0;
    }
    GEN_transmit(1,']');// debug
    GEN_transmit(1,13);// debug
    GEN_transmit(1,10);// debug
}

输出日志

SENDING [01 00 23 A4 1B 01 02 03 04 05 06 00 07 08 09 92 00 ]
[prfe evt: 12 PN:0 sys:1]
[TX:from <0xA423> to <0x0001> port=0 L_INDEX=0 L=0 F=0x92 PN=0x00 CRC8=0x93 #]

接收端日志

Error during GENet frame reception at index 17 from : 42019 to 1 error code : 1
 Received frame from <0xA423> to <0x0001> size [25] 010023A41B010203040506000708099200FFAA010023A41B01 [errors : 1]

问题解答

这种情况完全合理,核心原因是ARM 32位架构的内存对齐规则导致编译器自动插入了填充字节。

具体分析

  1. 计算CRCr前的字节数:
    dstAddr(2字节) + srcAddr(2字节) + D0-D6(7个uint8_t,共7字节) = 11字节。
  2. ARM对齐规则要求:
    ARM架构默认要求uint16_t类型数据必须对齐到2字节边界(即起始地址为偶数)。由于CRCr联合体内包含uint16_t CRC,编译器会在D6和CRCr之间填充1个字节,让CRCr的起始地址变为12(11+1,是2的倍数),这就是日志中出现的额外00字节。
  3. AVR架构无严格对齐:
    8位AVR架构没有强制内存对齐要求,编译器不会自动插入填充字节,因此代码在AVR上能正常运行。

解决方法

如果需要跨架构保证结构体内存布局一致,可采用以下两种方式:

  • 使用编译器指令强制无填充:
    ARM GCC环境下,可通过__attribute__((packed))修饰结构体,禁止编译器插入对齐填充字节。修改示例:
    struct GENet_header __attribute__((packed))
    {
        // 原有成员保持不变
    };
    
    注意:使用该属性会带来一定性能损失,因为ARM访问未对齐数据需要额外指令处理,但对于通信协议这类要求严格内存布局的场景,这种牺牲是必要的。
  • 手动管理内存偏移:
    不依赖结构体自动布局,通过手动计算偏移量访问每个成员,避免编译器自动填充影响。但这种方式代码可读性差,维护成本高,不推荐。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:59:50