在嵌入式开发时,常常需要注意结构体的字节对齐。当然了,不是说在PC上开发程序时不需要关注。只是两者关注的程度不一样。同时,不同的操作系统或者操作需求,字节对齐的原则也不一样。下面针对在PC上Microsoft Visual Stdio上分析结构体字节对齐的问题。
在使用sizeof运算符计算结构体占用的内存大小时,不是简单的将成员变量所占用的内存大小直接相加。这中间涉及到内存字节对齐的问题。为什么会存在字节对齐的问题呢?这是操作系统对内存操作、CPU机器周期等的优化的产物。如有的平台在地址为偶数的地方存取数据,而有的平台在奇数地址存取数据。如果在偶数地址存取数据的平台上,访问奇数地址的内容,则多需要一个读取指令。同样的问题会存在奇数地址存取的平台。为此有了内存对齐的概念。
以下在win32平台上讨论字节对齐的问题,其对齐策略有:
1)结构体变量的首地址能够被其最宽数据类型成员的大小整除。编译器在为结构体变量开辟空间时,首先找到结构体中最宽的数据类型,然后寻找内存地址能被该数据类型大小整除的位置,这个位置作为结构体变量的首地址。而将最宽数据类型的大小作为对齐标准。
struct
{
char a;
short b;
char c;
}stc_one;
&stc_one
0x00428278
&stc_one.a 0x00428278
&stc_one.b 0x0042827a
&stc_one.c 0x0042827c
可以看出这个结构体中最宽的数据类是short,占用2个字节。结构体变量的首地址可以被2整除。这个结构体是以2个字节为标准进行对齐的。
2)结构体每个成员相对结构体首地址的偏移量(offset)都是每个成员本身大小的整数倍,如有需要会在成员之间填充字节。编译器在为结构体成员开辟空 间时,首先检查预开辟空间的地址相对于结构体首地址的偏移量是否为该成员大小的整数倍,若是,则存放该成员;若不是,则填充若干字节,以达到整数倍的要 求。
3)结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍
struct
{
char a;
short b;
char c;
long d;
}stc_one;
该结构体中的最宽数据类型是long,其占用内存空间大小为4个字节,sizeof(stc_one) = 12.
下面结合实例一起看下sizeof是如何计算结构体占用的内存打下的。
1、空结构体:
struct
{
}stc_three;
在C++的编译中为1个字节,而在C编译器中为0字节。
2、
struct
{
char a;
int b;
short c;
}stc_four;
该结构体以int型为标准,即以4字节为标准进行内存对齐。所以sizeof(stc_four) = 12个字节。
3、含有静态数据成员(注意:在C语言中的结构体中,是不允许有静态成员的。只有在C++中才可以)
struct
{
int a;
long b;
static long c;
}stc_five;
成员变量c是存放在静态区,没有在stc_five变量的存储空间中,所以sizeof(stc_five) = 8个字节。
4、结构体中包含结构体
typedef struct
{
char a;
short b;
char c;
//long d;
}stc_one;
struct
{
bool a;
stc_one b;
int c;
}stc_six;
在stc_six中最宽的变量长度是stc_one,其为6个字节,所以在这6个字节中找最宽的变量长度作为stc_six的内存对齐标准。所以是2字节为标准对stc_six进行内存对齐的。故sizeof(stc_six) = 12个字节。
注意:不要将stc_one完全拆开来比较。
在此,需要将stc_one和stc_six中的最宽成员变量进行比较,选择大的作为内存对齐的标准。即,如果stc_one大于stc_six中的其他成员变量的空间时,选用stc_one中的最宽成员变量的占用内存大小最为标准进行内存对齐。如果stc_one小于stc_six中的其他成员变量的空间时,选用占用空间大的作为标准。
5、强制以某一个数值为标准进行内存对齐
若在程序中使用了#pragma pack(n)命令强制以n字节对齐时,默认情况下n为8.
则比较n和结构体中最长数据类型所占的字节大小,取两者中小的一个作为对齐标准。
若需取消强制对齐方式,则可用命令#pragma pack()
#pragma pack(1)
typedef struct
{
char a;
short b;
char c;
//long d;
}stc_one;
#pragma pack()
那么sizeof(stc_one) = 4个字节。