STM32 4字节对齐问题

本文探讨了在STM32项目中遇到的字节对齐问题,特别是double类型数据在不同内存地址上的赋值导致的HardFaultException异常,并通过分析底层汇编代码揭示了异常产生的根本原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近一个项目使用STM32,中间涉及到通信协议解析,协议中包含double类型数据传输,

unsigned char ReceiveBuffer[50];//为串口接收的数据,
其中Byte0为命令码,Byte1-Byte48为double数据,共计6个double数据,小端格式,Byte49位校验。
double *StoreDoublePointer;
StoreDoublePointer = (double )(ReceiveBuffer+1);
执行
StoreDoublePointer = 12.56;会进入HardFaultException函数。

之前遇到过类似的问题,所以当时肯定应该是字节对齐的问题。修改StoreDoublePointer = (double *)(ReceiveBuffer+3);执行没有问题,确定是这方面的问题。修改了方法,绕过了此问题,同样也实现了功能。

但是关于4字节对齐的问题,还没有仔细研究过;于是测试了如下代码:

unsigned char Temp[50];
double*pTempDouble;
pTempDouble = (double *)Temp;
*pTempDouble = 12.456;
*(pTempDouble + 1) = 0.6767;//运行没有问题。

pTempDouble = (double *)(Temp + 1);
*pTempDouble = 12;//进入HardFaultException中断。

至于为何只能得到是字对齐的问题,但具体是什么原因,不解。

后来,查看对应的汇编,如下:这里写图片描述
在ARMv7-M Architecture Application Level Reference Manual查STRD指令,只是一般性的介绍,后在Cortex™-M3 Revision: r1p1 Technical Reference Manual中查到STRD会引发异常,这里写图片描述,因此查看Usage Fault Status Register寄存器地址为0xE000ED2B, U16类型,上一个寄存器地址为0xE000ED29,因此此寄存器地址指向0xE000ED2A,刚进入HardFaultException中断,寄存器数值发生变化,这里写图片描述,与手册上描述的一致,因此是编译器使用了STRD指令,将double类型转为long,编译器采用的是STR指令,回到ARMv7-M Architecture Application Level Reference Manual手册中,也有类似的描述,这里写图片描述

<think>我们在处理STM32中的结构体字节对齐问题时,需要注意以下几点:1.结构体成员的对齐:通常编译器会按照成员中最大的基本类型进行对齐(在STM32开发中,默认是4字节对齐)。2.结构体总大小的计算:必须是其最大成员类型的整数倍。3.手动设置对齐:可以使用GCC的__attribute__((aligned(n)))来指定对齐方式,或者使用#pragmapack(n)来改变编译器的默认对齐方式。根据引用[1]和引用[3]的内容,结构体在STM32中需要注意字节对齐,否则在访问时可能会发生硬件错误(特别是在强制类型转换时)。下面我们给出一个示例代码,展示如何设置结构体字节对齐。我们将通过三种方式:方式1:使用__attribute__((aligned(4)))设置结构体对齐为4字节。方式2:使用#pragmapack(1)设置1字节对齐,然后恢复默认对齐。方式3:在结构体中添加一个无意义的32位成员,使其自然对齐。另外,根据引用[3],如果要将结构体存储到FLASH中,需要确保结构体的首地址是4字节对齐的,并且结构体内部也要符合对齐要求,否则可以尝试使用缓存数组的方式。示例代码:*///方式1:使用__attribute__((aligned(4)))structMyStruct1{charx1;//1字节shortx2;//2字节,需要对齐到2的倍数地址intx3;//4字节,需要对齐到4的倍数地址charx4;//1字节}__attribute__((aligned(4)));//整个结构体按照4字节对齐//方式2:使用#pragmapack改变对齐#pragmapack(push,1)//保存当前对齐,并设置为1字节对齐structMyStruct2{charx1;shortx2;//由于是1字节对齐,short可以出现在任意地址(但要注意,STM32是ARM架构,要求半字对齐,所以这种方式虽然编译器允许,但运行时可能会导致硬件错误)intx3;charx4;};#pragmapack(pop)//恢复之前的对齐//方式3:在结构体中添加一个无意义的32位成员,确保结构体自然对齐(首地址和大小都满足)structMyStruct3{uint32_tdummy;//作为第一个元素,确保结构体首地址是4字节对齐(如果结构体是定义在堆栈上,由编译器分配,则首地址自动对齐,但这里是为了保证内部成员对齐)charx1;shortx2;intx3;charx4;};/*注意:*方式2中的1字节对齐可能会导致访问成员时出现硬件错误,因为STM32要求半字(16位)访问必须是2字节对齐,字(32位)访问必须是4字节对齐。*因此,在STM32中一般不建议使用1字节对齐,而是使用默认的4字节对齐。另外,引用[3]提到了将结构体保存到FLASH的方法,这里给出一个使用中间数组的示例:1.定义结构体(默认4字节对齐)2.定义一个字节数组,使用__attribute__((aligned(4)))确保字节数组的地址是4字节对齐的。3.使用memcpy将结构体复制到字节数组,然后将字节数组写入FLASH。4.读取时,从FLASH读取到字节数组,再memcpy到结构体。例如:*/#include<string.h>structMyData{intid;floatvalue;charname[20];};//写入FLASH的函数(伪代码)voidWriteToFlash(uint32_taddr,void*data,uint32_tsize){//假设这个函数要求addr是4的倍数,data指向的缓冲区也是4字节对齐的。}//写入结构体的例子voidSaveMyData(uint32_tflash_addr,structMyData*data){//定义一个对齐的缓冲数组staticuint8_tbuffer[sizeof(structMyData)]__attribute__((aligned(4)));memcpy(buffer,data,sizeof(structMyData));WriteToFlash(flash_addr,buffer,sizeof(structMyData));}/*通过上述方法,可以确保写入和读取时地址的对齐。关于结构体大小计算教程:引用[1]中提到:整个结构体的实际内存消耗是9个字节,但结构体总大小为12个字节。我们以MyStruct1为例:MyStruct1:charx1;//1字节,位置0shortx2;//要求2字节对齐,所以不能放在1,而是放在2(因为位置1可能是空出的填充字节),所以位置0:x1,位置1:(填充),位置2-3:x2intx3;//要求4字节对齐,下一个位置是4,所以从位置4开始存放,占4-7charx4;//位置8//然后整个结构体需要对齐到4字节,所以总大小应该是4的倍数,目前是9,所以后面需要填充3个字节(位置9,10,11),总大小为12.可以使用sizeof运算符获取结构体的大小。注意:不同的编译器和编译选项可能导致不同的对齐结果。在STM32开发中,通常使用ARMCC或GCC,并且默认都是4字节对齐。下面我们用一个完整的例子来验证:*/#include<stdio.h>intmain(){//查看MyStruct1的大小printf("SizeofMyStruct1:%d\n",sizeof(structMyStruct1));//预期12//查看每个成员的偏移量structMyStruct1s1;printf("offsetofx1:%d\n",(char*)&s1.x1-(char*)&s1);//0printf("offsetofx2:%d\n",(char*)&s1.x2-(char*)&s1);//应为2printf("offsetofx3:%d\n",(char*)&s1.x3-(char*)&s1);//4printf("offsetofx4:%d\n",(char*)&s1.x4-(char*)&s1);//8return0;}/*注意:这个例子需要在STM32工程中运行,或者在PC上模拟(但结果可能因平台和编译器不同而有差异,在STM32中编译运行才准确)。总结:在STM32开发中,结构体字节对齐是一个重要的问题,特别是当结构体需要直接访问外设寄存器、进行DMA传输或者保存到FLASH时,必须确保结构体的地址和内部成员的对齐。为了避免问题,可以:1.使用默认对齐(4字节),并让编译器自动处理(确保结构体变量定义时地址对齐)。2.如果需要特殊对齐(如网络协议包需要1字节对齐),要格外小心,确保不会发生未对齐的访问(可以通过函数访问来避免直接访问)。3.存储到FLASH时使用缓冲数组,并确保缓冲数组4字节对齐。建议参考教程:1.ARM官方关于数据齐的文档。2.所使用的编译器(如GCC)关于对齐的说明文档。希望这些示例和解释能帮助你理解STM32中的结构体字节对齐问题。</think> 以下是关于STM32结构体字节对齐的详细说明和示例代码,结合引用内容的关键知识点: ### 一、字节对齐规则说明 1. **对齐原则**:结构体首地址自动4字节对齐(编译器保证)[^2] 2. **成员对齐规则**: - `char`(1B) 可出现在任意地址 - `short`(2B) 地址需是2的倍数 - `int/float`(4B) 地址需是4的倍数 3. **总大小计算**:结构体大小必须是最大成员类型的整数倍(需填充空字节)[^1] ### 二、示例代码(含详细注释) ```c #include <stdint.h> // 默认对齐(4字节对齐) typedef struct { char x1; // 1字节 @偏移0 short x2; // 2字节 @偏移2(中间1字节填充) int x3; // 4字节 @偏移4 char x4; // 1字节 @偏移8 // 末尾填充3字节(使总大小为12) } DefaultAlign; // sizeof = 12 // 1字节紧凑对齐(取消填充) #pragma pack(1) // 设置编译器1字节对齐 typedef struct { char x1; // @偏移0 short x2; // @偏移1(无填充) int x3; // @偏移3 char x4; // @偏移7 } PackedStruct; // sizeof = 8 #pragma pack() // 恢复默认对齐 // 手动添加对齐成员 typedef struct { uint32_t dummy; // 首个元素强制对齐 @偏移0 char x1; // @偏移4 short x2; // @偏移6(需2字节对齐) } ManualAlign; // sizeof = 8 // FLASH存储解决方案 void save_to_flash(void* data, uint32_t size) { uint8_t buffer[100] __attribute__((aligned(4))); // 强制4字节对齐[^3] memcpy(buffer, data, size); // 拷贝到对齐缓冲区 // HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, (uint32_t)buffer); } ``` ### 三、关键问题解决方案 1. **避免FLASH存储错误**: ```c // 错误:直接强制转换非对齐地址 uint32_t* ptr = (uint32_t*)&non_aligned_struct; // 可能导致硬件错误[^3] // 正确:使用对齐缓冲区 uint8_t aligned_buf[sizeof(MyStruct)] __attribute__((aligned(4))); memcpy(aligned_buf, &my_struct, sizeof(MyStruct)); ``` 2. **字节填充可视化**: ``` 默认对齐结构布局: [0]: char x1 [1]: <填充字节> [2-3]: short x2 [4-7]: int x3 [8]: char x4 [9-11]: <填充字节> // 总大小12[^1] ``` ### 四、相关教程资源 1. **[正点原子STM32教程]**:结构体内存对齐章节(免费PDF) 2. **[ST官方AN4449]**:*Structure Packing for Cortex-M Processors*(官网搜索) 3. **[博客园] STM32内存对齐详解**:含DMA传输实例(搜索标题)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值