参考链接:
结构体字节对齐_雨微尘的博客-CSDN博客_结构体1字节对齐
结构体字节对齐 在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何 变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这就是内存对齐。 内存对齐的原因: 1)某些平台只能在特定的地址处访问特定类型的数据; 2)提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。 win32平台下的微软C编译器对齐策略: 1)结构体变量的首地址能够被其最宽数据类型成员的大小整除。编译器在为结构体变量开辟空间时,首先找到结构体中最宽的数据类型,然后寻找内存地址能被该数据类型大小整除的位置,这个位置作为结构体变量的首地址。而将最宽数据类型的大小作为对齐标准。 2)结构体每个成员相对结构体首地址的偏移量(offset)都是每个成员本身大小的整数倍,如有需要会在成员之间填充字节。编译器在为结构体成员开辟空 间时,首先检查预开辟空间的地址相对于结构体首地址的偏移量是否为该成员大小的整数倍,若是,则存放该成员;若不是,则填充若干字节,以达到整数倍的要 求。 3)结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍。 下面看一下sizeof在计算结构体大小的时候具体是怎样计算的 1.test1 空结构体
则sizeof(S)=1;或sizeof(S)=0; 在C++中占1字节,而在C中占0字节。 2.test2
则sizeof(S1)=8。这是因为结构体node1中最长的数据类型是int,占4个字节,因此以4字节对齐,则该结构体在内存中存放方式为 |--------int--------| 4字节 |char|----|--short-| 4字节 总共占8字节 3.test3
则siezof(S3)=12.最长数据类型为int,占4个字节。因此以4字节对齐,其在内存空间存放方式如下: |char|----|----|----| 4字节 |--------int--------| 4字节 |--short--|----|----| 4字节 总共占12个字节 4.test4 含有静态数据成员
则sizeof(S3)=8.这里结构体中包含静态数据成员,而静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的)。其在内存中存储方式如下: |--------int--------| 4字节 |--short-|----|----| 4字节 而变量c是单独存放在静态数据区的,因此用siezof计算其大小时没有将c所占的空间计算进来。 5.test5 结构体中含有结构体
则sizeof(S4)=16。是因为s1占8字节,而s1中最长数据类型为int,占4个字节,bool类型1个字节,short占2字节,因此以4字节对齐,则存储方式为 |-------bool--------| 4字节 |-------s1----------| 8字节 |-------short-------| 4字节 6.test6
则sizeof(S5)=32。是因为s1占8字节,而s1中最长数据类型为int,占4字节,而double占8字节,因此以8字节对齐,则存放方式为: |--------bool--------| 8字节 |---------s1---------| 8字节 |--------double------| 8字节 |----int----|---------| 8字节 7.test7 若在程序中使用了#pragma pack(n)命令强制以n字节对齐时,默认情况下n为8. 则比较n和结构体中最长数据类型所占的字节大小,取两者中小的一个作为对齐标准。 若需取消强制对齐方式,则可用命令#pragma pack() 如果在程序开头使用命令#pragma pack(4),对于下面的结构体
则sizeof(S5)=24.因为强制以4字节对齐,而S5中最长数据类型为double,占8字节,因此以4字节对齐。在内存中存放方式为: |-----------a--------| 4字节 |--------s1----------| 4字节 |--------s1----------| 4字节 |--------b-----------| 4字节 |--------b-----------| 4字节 |---------c----------| 4字节 总结一下,在计算sizeof时主要注意一下几点: 1)若为空结构体,则只占1个字节的单元 2)若结构体中所有数据类型都相同,则其所占空间为 成员数据类型长度×成员个数 若结构体中数据类型不同,则取最长数据类型成员所占的空间为对齐标准,数据成员包含另一个结构体变量t的话,则取t中最 长数据类型与其他数据成员比较,取最长的作为对齐标准,但是t存放时看做一个单位存放,只需看其他成员即可。 3)若使用了#pragma pack(n)命令强制对齐标准,则取n和结构体中最长数据类型占的字节数两者之中的小者作为对齐标准。 另外除了结构体中存在对齐之外,普通的变量存储也存在字节对齐的情况,即自身对齐。编译器规定:普通变量的存储首地址必须能被该变量的数据类型宽度整除。 测试程序: |
总结一下,在计算sizeof时主要注意一下几点:
1)若为空结构体,则只占1个字节的单元
2)若结构体中所有数据类型都相同,则其所占空间为 成员数据类型长度×成员个数
若结构体中数据类型不同,则取最长数据类型成员所占的空间为对齐标准,数据成员包含另一个结构体变量t的话,则取t中最 长数据类型与其他数据成员比较,取最长的作为对齐标准,但是t存放时看做一个单位存放,只需看其他成员即可。
3)若使用了#pragma pack(n)命令强制对齐标准,则取n和结构体中最长数据类型占的字节数两者之中的小者作为对齐标准。
另外除了结构体中存在对齐之外,普通的变量存储也存在字节对齐的情况,即自身对齐。编译器规定:普通变量的存储首地址必须能被该变量的数据类型宽度整除。
#include <iostream>
usingnamespacestd;
//#pragma pack(4) //设置4字节对齐
//#pragma pack() //取消4字节对齐
原文链接:
今天给大家带来一道经典、易错的关于C语言结构体内存对齐的题目:
求32bit环境下以下结构体所占的字节数:
typedef struct test_struct
{
char a;
short b;
char c;
int d;
char e;
}test_struct;
请说出你的答案:
下面看一下实际测试情况:
1、测试代码:
/***********************************
* 公众号:嵌入式大杂烩
***********************************/
#include <stdio.h>
typedef struct test_struct
{
char a;
short b;
char c;
int d;
char e;
}test_struct;
int main(void)
{
test_struct test_s;
printf("\n============================================\n");
printf("test_s addr = %#.8x\n", &test_s);
printf("test_s.a addr = %#.8x\n", &test_s.a);
printf("test_s.b addr = %#.8x\n", &test_s.b);
printf("test_s.c addr = %#.8x\n", &test_s.c);
printf("test_s.d addr = %#.8x\n", &test_s.d);
printf("test_s.e addr = %#.8x\n", &test_s.e);
printf("sizeof(test_s) = %d\n", sizeof(test_s));
printf("============================================\n");
return 0;
}
2、运行结果
在32bit环境中,该结构体所占的字节数为16。答对了吗?
运行结果打印输出了很多重要的信息,从结果往前分析思路应该很清晰了吧?
下面,我们一起来分析分析。
3、分析
在分析这个问题之前,我们先记住关于结构体内存对齐的三条原则:
(1)结构体变量的起始地址
能够被其最宽的成员大小整除。
(2)结构体每个成员相对于起始地址的偏移
能够被其自身大小整除
,如果不能则在前一个成员后面补充字节
。
(3)结构体总体大小能够被最宽的成员的大小整除
,如不能则在后面补充字节
。
分析这个问题我们就不考虑编译器可以指定对齐大小
的情况了。在32bit环境中,一般默认的对齐大小是4。
下面,我们根据这三条原则来分析,并得出如下示意图:
从这张图中,我们应该可以很清晰地看出整个结构体变量的内存占用情况。
如果还看不明白的朋友,可以阅读下面的解释(有点啰嗦,已经看明白的就不用看了~):
从上例的结果中,我们结构体变量test_s的起始地址为0x0028ff30,能够被其最宽的成员(int类型的d成员,占4个字节)整除,符合第(1)条原则。
a成员的地址即为结构体变量的起始地址0x0028ff30,排在a后面的是short类型(两个字节)的b成员。
根据第(2)条规则,显然b的地址不能从0x0028ff31开始,则编译器会在b成员的前一个成员(a成员)后边补1个空白字节,即b的的地址为从0x0028ff32,符合规则(2)。
b成员占两个字节,两个字节之后的地址为0x0028ff34,而c成员为char类型(1字节),则根据规则(2),c成员会存放至地址0x0028ff34处。
c成员占1个字节,1个字节之后的地址为0x0028ff35,排在c后面的是int类型(4个字节)的d成员,显然不能满足规则(2)。
编译器会在d成员的前一个成员(c成员)后面进行字节填充,这里必须填充3个字节才能符合规则(2),此时d会存放至地址0x0028ff38处。
d成员占4个字节,4个字节之后的地址为0x0028ff3c。根据规则(2),e成员可从该地址开始存放。
此时a+空白字节+b+c+空白字节+d+e
所占的字节总数为13个字节,而结构体最宽的成员(int类型的d成员)所占字节数为4字节。
显然不能满足规则(3),编译器会在e成员后面填充3个字节。即整个结构体变量test_s所占的总字节数为16字节。
4、实际应用
(1)用保留变量替代填充字节
实际应用中,我们可以上面的结构体变量改为:
typedef struct test_struct
{
char a;
char reserve0; /* 保留成员 */
short b;
char c;
int d;
char e;
char reserve1[3]; /* 保留成员 */
}test_struct;
我们已经知道了编译器会自动给我们的结构体变量填充一些空白字节,这些填充字节我们是看不到的,是隐性的。
在结构体变量占用相同内存的情况下,我们可以显性的表示出这些填充字节,即创建一些保留成员 。
这样,当我们需要给这个结构体添加一些成员时,我们可以把保留的成员替换为实际的成员。这样在一定程度下有利于我们节省内存空间。
(2)调整结构体成员的位置
从上面的分析中,我们知道编译器会根据我们结构体成员的排列来进行空白字节填充以达到对齐的效果。
那么,我们自己进行手动对齐一些成员,那就可以节省一些空间了。比如把上面的我们的test_struct结构体成员的顺序改为:
typedef struct test_struct
{
char a;
char c;
short b;
int d;
char e;
}test_struct;
则结构体变量test_s所占的字节数变为12字节,即:
即比原来的16字节省下了4个字节。
虽然这点优化对于一般的嵌入式应用来说可能没什么必要,但是万一某一天真的需要在某些资源极其受限的嵌入式设备中开发应用,这就是可以优化的一点。
最后
以上就是本次的实验分享。如有错误,欢迎指出!谢谢
这道结构体内存对齐的题目很经典、也很容易出错,是嵌入式C语言笔试、面试题中的高频题目,很有必要弄清楚。