STM32 结构体对齐规则

发布者:悠然自在最新更新时间:2021-02-06 来源: eefocus关键字:STM32  结构体  对齐规则 手机看文章 扫描二维码
随时随地手机看文章

1、对齐算法


在相同的对齐方式下,结构体内部数据定义的顺序不同,结构体整体占据内存空间也不同。


如下结构体定义:


struct A {

// a 的自身对齐值为 4,偏移地址为 0x00~0x03,a 的起始地址 0x00 满足 0x00%4=0

int a;

 

// b 的自身对齐值为 1,由于紧跟 a 之后的地址,即 0x04 满足 0x04%1=0,所以 b 存放在 0x04 地址空间

char b;

 

// c的自身对齐值为2,由于紧跟b之后的地址0x05%2不是0,而0x06%2=0

// 因此c的存放起始地址为0x06,存放在0x06~0x07空间。

// 在b和c之间的0x05地址则补空字节

short c;

};

结构体A中包含:


1)4 字节长度的 int;


2)1 字节长度的 char;


3)2 字节长度的 short 型数据一个。


所以 A 用到的空间应该是 7 字节。


但是因为编译器要对数据成员在空间上进行对齐,由于结构体自身对齐值取数据成员中自身对齐值的最大值,即 4,并且 0x00~0x07 的 8 字节空间满足 8%4=0,所以 sizeof(strcut A) 值为 8。


现在把该结构体调整成员变量的顺序:


struct B {

// b的自身对齐值为1,其起始地址为0x00,由于满足0x00%1=0,所以b存放在0x00地址空间

char b;

 

// a的自身对齐值为4,由于紧跟b之后的地址0x01%4不是0,而0x04%4=0

// 因此c的存放起始地址为0x04,存放在0x04~0x07空间。

// 在b和a之间的0x01~0x03地址则补3个空字节。

int a;

 

// c的自身对齐值为2,由于紧跟a之后的地址0x08%2=0,因此c的存放起始地址为0x08,存放在0x08~0x09空间        

short c;          

};

这时候同样是总共7个字节的变量,但是由于结构体自身对齐值取数据成员中自身对齐值的最大值,即4;


并且0x00~0x09的10字节空间不满足10%4=0,而12%4=0,所以sizeof(struct B)的值却是12,即在紧跟c之后的0x0A~0x0B地址还需补两个空字节,使得整个结构体占用的字节空间为12个字节。

 

下面我们使用预编译指令#pragma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。


#pragma pack (2) /*指定按2字节对齐,等价于#pragma pack(push,2)*/

struct C {

char b;

int a;

short c;

};

#pragma pack () /*取消指定对齐,恢复缺省对齐,等价于#pragma pack(pop)*/

sizeof(struct C) 值是8。


修改对齐值为1:


#pragma pack (1) /*指定按1字节对齐*/

struct D {

char b;

int a;

short c;

};

#pragma pack () /*取消指定对齐,恢复缺省对齐*/


sizeof(struct D) 值为7。


对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,long类型,其自身对齐值为4,double,long long类型,其自身对齐值为8单位字节。


2、四个概念值


1)数据类型自身的对齐值,就是上面交代的基本数据类型的自身对齐值;


2)指定对齐值:#pragma pack (value)时的指定对齐值value;


3)结构体或者类的自身对齐值:其数据成员中自身对齐值最大的那个值;


4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值;


有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。


有效对齐值N是最终用来决定数据存放地址方式的值,最重要。


有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0"。


而数据结构中的数据变量都是按定义的先后顺序来排放的。


第一个数据变量的起始地址就是数据结构的起始地址。


结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整,就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍。


这样就不难理解上面的几个例子的值了。


例子分析:


struct B {

char b;

int a;

short c;

};

假设B从地址空间0x0000开始排放。


该例子中没有定义指定对齐值,该值默认为4。


第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.


第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,符合0x0004%4=0, 且紧靠第一个变量。


第三个变量c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。


再看数据结构B的自身对齐值为其变量中最大对齐值(这里是a)和指定对齐值(这里是4)中较小的那个,所以就是4,所以结构体的有效对齐值也是4。


根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。


所以0x0000A到0x000B也为结构体B所占用,故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12;、


#pragma pack (2) /*指定按2字节对齐*/

struct C {

char b;

int a;

short c;

};

#pragma pack () /*取消指定对齐,恢复缺省对齐*/


第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;


第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。


第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x0006、0x0007中,符合0x0006%2=0。

所以从0x0000到0x00007共八字节存放的是C的变量,又C的自身对齐值为4;

所以C的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

 

更改c编译器的缺省字节对齐方式:


在缺省情况下,c编译器为每一个变量或数据单元按其自然对界条件分配空间;


一般地可以通过下面的两种方法来改变缺省的对界条件:


方法一:


使用#pragma pack(n),指定c编译器按照n个字节对齐;

使用#pragma pack(),取消自定义字节对齐方式。


方法二:


__attribute(aligned(n)),让所作用的数据成员对齐在n字节的自然边界上;


如果结构中有成员的长度大于n,则按照最大成员的长度来对齐;


__attribute((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。


比如:


typedef struct

 

{

  ...

 

}__attribute__((aligned(4))) param_t;

3、示例


例子一:


#pragma pack(4)

class TestB

{

   public:

   int aa;   //第一个成员,放在[0,3]偏移的位置,

  char a;    //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。由于下一成员short b是按2字节对齐,因此char a后面只需补一个空字节

  short b; //第三个成员,自身长2,#pragma pack(4),取小值为2,按2字节对齐,所以放在偏移[6,7]的位置。

  char c;   //第四个,自身长为1,放在[8]的位置。

};

可见,此类实际占用的内存空间是9个字节。


根据规则5,结构整体的对齐是min( sizeof( int ), pack_value ) = 4,所以sizeof( TestB ) = 12; 


char a 占用一个字节,后面补3个空字节;


short b和char c可以放在同一4字节空间中,后面只需补一个空字节。


例子二:


#pragma pack(2)

class TestB

{

   public:

      int aa;   //第一个成员,放在[0,3]偏移的位置,

  char a;   //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。由于下一成员short b按2字节对齐,因此char a后面只需补一个空字节

  short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。

  char c;   //第四个,自身长为1,放在[8]的位置。

};

可见结果与例子一相同,各个成员的位置没有改变,此时结构整体的对齐是min( sizeof( int ), pack_value ) = 2,所以sizeof( TestB ) = 10;char a后面补一个空字节;char c后面补一个空字节。


例子三:


#pragma pack(4)

class TestC

{

  public:

     char a;    //第一个成员,放在[0]偏移的位置,由于下一成员short b按2字节对齐,因此char a后面只弱补一个空字节

  short b; //第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。

  char c;   //第三个,自身长为1,放在[4]的位置。

};

整个类的实际内存消耗是5个字节,整体按照min( sizeof( short ), 4 ) = 2对齐,所以结果是sizeof( TestC ) = 6。


例子四:


struct Test

{

    char x1;   //第一个成员,放在[0]位置。由于下一成员short x2按2字节对齐,因此char x1后面只需补一个空字节,

    short x2; //第二个成员,自身长度为2,按2字节对齐,所以放在偏移[2,3]的位置,

    float x3;  //第三个成员,自身长度为4,按4字节对齐,所以放在偏移[4,7]的位置,

    char x4;  //第四个成员,自身长度为1,按1字节对齐,所以放在偏移[8]的位置,

};

所以整个结构体的实际内存消耗是9个字节,考虑到结构整体的对齐是4个字节,所以整个结构占用的空间是12个字节。char x1和short x2共用一个4字节空间,后面补一个空字节;char x4后面需要补3个空字节。


例子五:


#pragma pack(8)

 

struct s1

{

    short a; //第一个,放在[0,1]位置,

    long b;  //第二个,自身长度为4,按min(4, 8) = 4对齐,所以放在[4,7]位置

};

所以结构体的实际内存消耗是8个字节;


结构体的对齐是min( sizeof( long ), pack_value ) = 4字节,所以整个结构占用的空间是8个字节。


struct s2

{

char c;         //第一个,放在[0]位置,

s1 d;           //第二个,根据规则四,对齐是min( 4, pack_value ) = 4字节(由于s1占用了8个字节,这里为什么不是8字节??),所以放在[4,11]位置,

long long e; //第三个,自身长度为8字节,所以按8字节对齐,所以放在[16,23]位置,

};

所以实际内存消耗是24字节。整体对齐方式是8字节,所以整个结构占用的空间是24字节。


#pragma pack()


所以:sizeof(s2) = 24, char c后面是空了3个字节接着是s1 d, s1 d后面又补了4个空字节,char c占一个字节,后面补7个字节;s1 d占8个字节;long long e占8个字节。



refer:


https://www.cnblogs.com/King-Gentleman/p/5297355.html


关键字:STM32  结构体  对齐规则 引用地址:STM32 结构体对齐规则

上一篇:STM32 启动代码汇编指令详解
下一篇:STM32 编译指令 #pragma pack 的配对使用

推荐阅读最新更新时间:2024-11-16 23:07

STM32 AT24C128简单读写程序
目前个人测试过AT24C02-AT24C128,感觉还可以。里面没有页写函数,有兴趣的伙伴可以补充一下,该程序仅供参考 单片机源程序如下: #include led.h #include delay.h #include sys.h #include usart.h #include key.h #include 24cxx.h #include myiic.h //要写入到24c128的字符串数组 const u8 TEXT_Buffer ={ STM32 AT24c128 }; #define SIZE sizeof(TEXT_Buffer) #define ADDRESS 163 //读
[单片机]
STM32——SPI通信协议(W25Q128FLASH的通信)
SPI协议 串行外设接口(Serial Peripheral Interface Bus,SPI),是一种用于短程通信的同步串行通信接口规范,主要应用于单片机系统中。类似I2C。 这种接口首先被Motorola(摩托罗拉)公司开发,然后发展成了一种行业规范。典型应用包含SD卡和液晶显示器。 SPI设备之间使用全双工模式通信,包含一个主机和一个或多个从机。主机产生待读或待写的帧数据,多个从机通过一个片选线路 决定哪个来响应主机的请求。 SPI物理层 SPI 通讯设备之间的常用连接方式 SS( Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS、CS。当有多个 SPI 从设备与 SPI 主机相连时,
[单片机]
<font color='red'>STM32</font>——SPI通信协议(W25Q128FLASH的通信)
STM32自学手册之GPIO
GPIO是STM32最常用的设备之一。STM32可以提供最多达80个双向IO口(视型号而定),他们分别分布在A-E五个端口中。每个端口有16个IO,每个IO口都可以承受最大为5V压降。通过GPIO的配置寄存器,我们可以把GPIO口配置成我们想要的工作模式,一共有如下8种模式: ● 浮空输入 ● 带上拉电阻的输入 ● 带下拉电阻的输入 ● 模拟输入 ● 开漏输出 ● 推挽输出 ● 复用推挽输出 ● 复用开漏输出 STM32的GPIO除了上述8种工作模式之外,还可以进行两种映射:外部中断映射和第二功能映射(也叫重映射)。当某个IO口映射为外部中断通道后,该IO口就成为一个外部中断源,我们可以在这个I
[单片机]
<font color='red'>STM32</font>自学手册之GPIO
STM32 | 硬件SPI主从通信
例子说明及框图 本例子基于STM32F103ZET6芯片,实现SPI1与SPI2的主从通信。其中SPI1配置为主机,SPI2配置为从机,均配置为全双工模式。硬件连接图: 其中,我们需要注意的是,SPI的从机不能主动发送数据,只能应答数据。本例子的数据交互过程: 主机使用查询方式发送数据给从机。 从机使用中断接收方式接收数据,把接收到的数据加上0x05再发送给主机。 从机总是在收到主机的数据时,才会发送数据给从机。即从机被动发送数据,也即主机主动申请数据。 代码细节 主函数: int main(void) { uint8_t i = 0; //---------------------------
[单片机]
<font color='red'>STM32</font> | 硬件SPI主从通信
STM32学习笔记:基础例子
本例子代码参考了STM32库开发实战指南中的代码,由于使用的板子是尚学STM32F103ZET6,为了配合板上已有资源,也参考了其配套代码。为了便于书写文本,我尽量将代码都写到了一个文件中,这种方式是不推荐的,在做具体工程时最好代码分类管理,使工程逻辑清晰。 现在对板上一些资源说明:板上有两个LED灯,引脚为PE5、PE6,均为ResetBits时点亮。有三个按钮,依次为黄色复位,红色PE4(按下接GND)、红色PA0(按下接3.3V,WAKE UP按钮)。ISP口为靠近电源开关的USB,也是USART1口。USART2口为PA3(Rx)、PA2(Tx)。IPD为高电平中断(按键一边接高电平),IPU为低电平中断。 接下来
[单片机]
<font color='red'>STM32</font>学习笔记:基础例子
STM32-如何使用引脚复用功能输出PWM
#define LED0_PWM_VAL TIM3- CCR2 //比较寄存器(TIM3通道2),可以调节PWM占空比, int main(void) { u16 led0pwmval=0; u8 dir=1; Stm32_Clock_Init(9); //系统时钟设置 delay_init(72); //延时初始化 PWM_Init(900,0);//设置PWM频率,不分频 while(1) { delay_ms(10); if(dir)led0pwmval++; else led0pwmval--; if(led0pwmval 300)dir=0; if(led0pwmval==0)dir=1; LED0
[单片机]
怎样用STM32单片机的GPIO控制LED显示状态
用STM32 的配置GPIO 来控制LED 显示状态,可用ODR,BSRR,BRR 直接来控制引脚输出状态。 ODR寄存器可读可写:既能控制管脚为高电平,也能控制管脚为低电平。 管脚对于位写1 gpio 管脚为高电平,写 0 为低电平 BSRR 只写寄存器:[color=Red]既能控制管脚为高电平,也能控制管脚为低电平。 对寄存器高 16bit 写1 对应管脚为低电平,对寄存器低16bit写1对应管脚为高电平。写 0 ,无动作BRR 只写寄存器:只能改变管脚状态为低电平,对寄存器 管脚对于位写 1 相应管脚会为低电平。写 0 无动作。 刚开始或许你跟我一样有以下疑惑: 1.既然ODR 能控制管脚高低电平为什么还需
[单片机]
怎样用<font color='red'>STM32</font>单片机的GPIO控制LED显示状态
STM32中采用DMA实现方波的产生和捕获
1 STM32微控制器介绍 STM32系列微控制器是ST公司基于Cortex-M3内核的高集成度的微控制器。它在性能、价格、功耗和实时性方面树立了一个新的标杆,集成了Cortex-M3内核,以及双ADC、多用途的通用时钟TIMx、RTC、I2C、SPI、UART、CAN、DMA、USB等丰富的外设。其功耗在全速72MHz所有模块都打开时也仅仅为36 mA,在低功耗模式下其功耗仅为2μA。 2 DMA和TIMx简介 STM32系列微控制器均含有DMA和通用时钟TIMx模块。其低端型号中仅包含DMA1,支持7个通道;高端型号还包括DMA2,支持5个通道。它的每个通道可任意指定工作模式,如内存到内存、内存到外设或外设到内存等。当
[单片机]
<font color='red'>STM32</font>中采用DMA实现方波的产生和捕获
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved