C语言结构体/联合体大小、非对齐定义与非对齐访问
1、结构体/联合体在ram空间占用大小的计算
示例1:
struct DEMO_S{
unsigned char U8_D;
unsigned short U16_D;
}DEMO_S;
示例2:
struct DEMO_S{
unsigned short U16_D;
unsigned short U16_D;
}DEMO_S;
示例3:
struct DEMO_S{
unsigned short U16_D;
unsigned char U8_D;
}DEMO_S;
根据结构体对齐规则,以上3个结构体在ram空间中占用大小均为4字节。
2、联合体套用结构体是字节大小的确定
依旧是一个例子:
typedef union DEMO_U
{
struct DEMO_S{
unsigned char a;
unsigned short b;
}DEMO_S;
unsigned char buffer[LENGTH];
}DEMO_U;
这个联合体定义中,LENGTH
应该设置为多大呢?
显然根据上文描述,应该设置为#define LENGTH 4
.
如果设置为3的话,存储地址就会有偏差.
如果按照长度为3定义联合体,内部内存的分配如下:
起始地址 | 起始地址 |
---|---|
buffer_0 | 因对齐产生的保留字节 |
buffer_1 | a |
buffer_2 | b_h |
无效 | b_l |
当然,根据平台大小端不一致,顺序会有更改,上表格为嵌入式平台的大端存放模式。
在PC端的小端存放模式下,存储顺序如下:
起始地址 | 起始地址 |
---|---|
buffer_0 | a |
buffer_1 | 因对齐产生的保留字节 |
buffer_2 | b_l |
无效 | b_h |
进行验证:
DEMO_U demo;
demo.DEMO_S.a=0x78;
demo.DEMO_S.b=0x1234;
printf("%x \r\n",demo.buffer[0]);
printf("%x \r\n",demo.buffer[1]);
printf("%x \r\n",demo.buffer[2]);
在PC端打印的就是:
0x78
0x00
0x34
在嵌入式平台上打印的就是:
0x00
0x78
0x12
3、为什么要这样子定义联合体
主要在嵌入式平台,比如FLASH存储数据、串口数据发送、网络数据发送等必须以字节为单位进行数据传输的场合,使用这种方法在编程上更好。
比如有一个程序功能模块,需要进行掉电信息存储,掉电存储的内容有两个。8bit的月份,16bit的年份,那么如下进行定义:
typedef union FLASH_U
{
struct FLASH_S{
unsigned char month;
unsigned short year;
}FLASH_S;
unsigned char buffer[4];
}FLASH_U;
FLASH_U flash_inf;
这样在软件功能内,修改年份信息可直接操作flash_inf.FLASH_S.year
.
在进行flash保存时,可直接操作flash_inf.buffer
进行保存.
一般FLASH、EEPROM都是以字节为单位进行存储的。
4、结构体/联合体的非对齐定义
上述所讲的都是默认编程情况下编译器分配内存时的形式。
但是可以通过预处理指令修改对齐大小的方式,来改变这种形式。
c语言 结构体内存对齐(含修改默认对齐数)
修改对齐大小的预处理指令如下:
#pragma pack(N)
#pragma pack()
举个例子:
#pragma pack(1)
struct DEMO_S{
unsigned char U8_D;
unsigned short U16_D;
}DEMO_S;
#pragma pack()
此时对齐数为1,所以结构体占RAM大小为3B。
#pragma pack(2)
struct DEMO_S{
unsigned char U8_D;
unsigned short U16_D;
}DEMO_S;
#pragma pack()
此时对齐数为2,所以结构体占RAM大小为4B。
5、非对齐访问
非对齐内存访问,会发生在如上述非对齐定义的情况下,因为限定了对齐大小,所以结构体成员的内存位置不可控。不过一般情况编译器实际上会为我们生成额外的指令去完成不会触发非对齐的数据访问方式。
但是当使用强制类型进行转换就有很大的几率产生非对齐访问导致的异常了。
uint16_t testdata=0;
void test_fun(uint32_t *indata)
{
*indata += 1;
}
void main()
{
/**/
test_fun((uint32_t *)&testdata);
/**/
}
上述程序内容,如果在支持非对齐访问的平台上,是不会有问题的。
但是如果在不支持非对齐访问的平台上,就会有两个可能性。
1)testdata的地址可以整除平台位数(比如4字节)
2)testdata的地址不可以整除平台位数
情况1可以正常运行,而情况2不可以正常运行。
所有平台都支持非对齐访问的!
比如cortex m0内核的MCU就不能进行非对齐访问,因为cortex m0所使用的arm v6M架构不支持内存的非对齐访问,如果强制进行内存的非对齐访问,会产生硬件错误中断
。
在cortex m0内核权威指南中有如下描述:
Cortex M3/4则可以进行非对齐访问,所以可以进行非对齐访问。
在cortex m3内核权威指南中有所描述(CM4内核也类似),如CM3内核权威指南<非对齐数据传送>:
6、8位MCU平台
8位MCU如80C51、STM8之类的的MCU,经过验证,不存在字节对齐,所以默认都是1字节对齐的。
主要可能原因是因为32位MCU对内存访问的性能要求比较高,所以需要内存对齐。