含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位架构的内存对齐规则导致编译器自动插入了填充字节。
具体分析
- 计算
CRCr前的字节数:dstAddr(2字节) +srcAddr(2字节) + D0-D6(7个uint8_t,共7字节) = 11字节。 - ARM对齐规则要求:
ARM架构默认要求uint16_t类型数据必须对齐到2字节边界(即起始地址为偶数)。由于CRCr联合体内包含uint16_t CRC,编译器会在D6和CRCr之间填充1个字节,让CRCr的起始地址变为12(11+1,是2的倍数),这就是日志中出现的额外00字节。 - AVR架构无严格对齐:
8位AVR架构没有强制内存对齐要求,编译器不会自动插入填充字节,因此代码在AVR上能正常运行。
解决方法
如果需要跨架构保证结构体内存布局一致,可采用以下两种方式:
- 使用编译器指令强制无填充:
ARM GCC环境下,可通过__attribute__((packed))修饰结构体,禁止编译器插入对齐填充字节。修改示例:
注意:使用该属性会带来一定性能损失,因为ARM访问未对齐数据需要额外指令处理,但对于通信协议这类要求严格内存布局的场景,这种牺牲是必要的。struct GENet_header __attribute__((packed)) { // 原有成员保持不变 }; - 手动管理内存偏移:
不依赖结构体自动布局,通过手动计算偏移量访问每个成员,避免编译器自动填充影响。但这种方式代码可读性差,维护成本高,不推荐。
内容的提问来源于stack exchange,提问作者bajtec
相关产品推荐
相关产品推荐

