序
最近的笔试题中做到了这样一个题目:
struct S
{
char c;
int a;
struct S *p;
union
{
short b;
unsigned int d;
};
};
该结构体编译到32位系统下的大小是()字节
下面就对结构体struct和共用体union大小一个简单的总结。
环境:Intel Core i5-4590,OS-64bit,CodeBlocks 16.01-32bit
1. struct
计算结构体的大小就是要看结构体是如何对齐的,结构体的对齐也可以说是内存对齐,为什么要进行内存对齐呢,主要是为了方便CPU寻址。
内存对齐关键有以下5点:
(1) 内存对齐与编译器设置有关,所以首先要搞清楚编译器这个默认值是多少;
(2) 如果不想按照编译器默认对齐的话,可以通过伪指令#pragma pack(n)指定按照n字节对齐;
(3) 结构体变量对齐遵循最小化长度规则。
(4) 结构体的大小确定:对齐后的长度必须是成员中最大的对齐参数的整数倍,最大对齐参数由(3)得到;
(5) 如果结构体A里面还有结构体B,则结构体B的对齐方式选取它里面最长的成员的对齐方式。
注:
最小化长度规则:每个结构体变量的对齐,如果对齐参数n大于该变量所占字节数m,则按照m对齐,也就是内存偏移后的地址是m的倍数,否则按照n对齐,即内存偏移后的地址是n的倍数。
所以结构体的对齐有这样的三步:
首先确定当前程序的对齐参数;接着计算每个结构体变量的大小和偏移量;最后计算结构体总大小。
默认对齐方式 + 指定对齐方式
(1) 默认对齐方式 - 自然对齐
在默认对齐方式下,结构体成员的内存分配满足下面三个条件
(1) 结构体的首地址就是结构体第一个成员的地址
(2) 结构体每个成员地址相对于结构体首地址的“偏移量”是该成员大小的整数倍,如果不是则编译器会在成员之间添加填充字节;
(3) 结构体“总的大小”要是其成员中最大size的整数倍,如果不是编译器会在其末尾添加填充字节。
struct s1
{
char a;
int b;
double c;
char d;
};
struct s2
{
char a;
int b;
double c;
};
输出结果:
s1结构体大小为24
a的偏移量为0
b的偏移量为4(按照默认对齐方式(2),编译器在成员a后边填充了3字节)
c的偏移量为8
d的偏移量为16
总长为24(按照默认对齐方式(3),最大成员c占用8字节,在结构体后边添加7字节)
s2结构体的大小为16
(2) 指定对齐方式
对于指定的对齐方式,其成员的地址偏移以及结构的总的大小按指定方式对齐
(1) 结构体第一个成员的地址和结构体的首地址相同
(2) 结构体每个成员的"地址偏移"需要满足最小化长度原则
如果n大于结构体成员中最大成员的大小,则n不起作用,仍然按照默认方式对齐
(3) 结构体总的大小需要是n的整数倍,如果不是需要在结构体的末尾进行填充
可以通过下面的方法改变缺省的对齐方式
使用伪指令#pragma pack (n),编译器将按照n个字节对齐;
使用伪指令#pragma pack (),取消自定义字节对齐方式。
#pragma pack (n)
struct aligntype
{
char a;
int b;
char c;
};
#pragma pack ()
当n为4、8、16时,其对齐方式均一样,sizeof(aligntype)的结果都等于12。而当n为2时,其发挥了作用,使得sizeof(aligntype)的结果为8。
现仍以默认对齐方式中的结构体为例
#pragma pack(4)
struct s1
{
char a;
int b;
double c;
char d;
};
struct s2
{
char a;
int b;
double c;
};
#pragma pack()
输出结果:
s1结构体大小为20
a的偏移量为0
b的偏移量为4(指定对齐方式,编译器在成员a后边填充了3字节)
c的偏移量为8
d的偏移量为16
总长为24(指定对齐方式,n=4小于最大成员c长度起作用,在结构体后边添加3字节)
s2结构体的大小为16
#pragma pack (2) /*指定按2字节对齐*/
struct S
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
结构体地址/大小分析:
第一个变量b的自身对齐值为1,指定对齐值为2,假设S从0x0000开始,那么b存放在0x0000
第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中
第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x0006、0x0007中
所以从0x0000到0x00007共八字节存放的是S的变量
又S的自身对齐值为4,所以S的有效对齐值为2(最小化长度规则)
又8%2=0,S只占用0x0000到0x0007的八个字节,所以sizeof(struct S) = 8.
注:
(1) 在使用#pragma pack(n)设定对齐方式一定要是2的整数幂,也就是(1,2,4,8,16,…),不然不起作用的,仍然按照默认方式对齐。
(2) 当结构体中有其他的结构体作为成员时,计算最大成员是不能把结构体成员作为一个整体来计算,要看其每个成员的大小,使用结构体中最长成员的对齐方式。这个将在下面结构体嵌套的例子中进行说明
- 结构体嵌套
struct stu1
{
short i;
struct
{
char c;
int j;
}ss;
int k;
};
结构体stu1的大小为16
结构体成员ss.c的偏移量应该是4,而不是 2。
#pragma pack(push)
#pragma pack(8)
struct s1
{
int a;
char b;
int c[20];
long l;
};
struct s2
{
char a1;
char a2;
struct s1 t1;
double b1;
};
#pragma pack(pop)
结构体s1的大小为92
a的偏移量为0
b的偏移量为4
c的偏移量为8(8大于int变量大小,以默认方式对齐;区分偏移量和成员大小)
l的偏移量为88
结构体s2的大小为104
a1的偏移量为0
a2的偏移量为1
结构体t1的偏移量为4(t1里面最长成员的“对齐方式”)
b1的偏移量为96
2. union
struct中的每个域在内存中都独立分配空间
union只分配最大域的空间,所有域共享这个空间
#include <stdio.h>
struct A
{
char a;
int b;
double c;
};
union B
{
char a;
int b;
double c;
};
int main()
{
printf("%d\n", sizeof(struct A));
printf("%d\n", sizeof(union B));
return 0;
}
输出结果:
结构体A的大小为16
结构体B的大小为8
对于stuct对齐
(1) 先算对齐大小:对齐的大小也是取决于struct成员中字节对齐最大的那个
(2) 再算成员空间:然后根据每个成员的对齐大小对齐每个成员算出分配的空间
(3) 最后算出实际分配的空间,分配struct的实际大小不小于成员空间总和且满足是对齐大小的整数倍
对于union对齐
(1) 先算对齐大小:对齐的大小是取决于union成员中字节对齐最大的那个
(2) 再算实际分配的空间:分配给union的实际大小不能小于最大成员的大小,且要满足是对齐大小的整数倍。
- 默认对齐
union U1
{
char a[9];
int b;
};
成员a是char数组,对齐大小为1字节,成员b是int,对齐大小为4字节,所以U1对齐大小为4字节;
分配给U1的实际大小既要是4字节的整数倍,又要不小于最大成员a的大小,即位4的整数倍又要大于9,所以实际分配的空间为12字节。
若将int改为double,则对齐为8,大小为16。
union U2
{
U1 a;
double b;
};
对比char,int和double,对齐是double的大小,为8,大小应当为是16。
- 指定对齐
#pragma pack(4) // pack(0)会采用默认的字节4
union U3
{
char a[9];
double b;
};
基本元素b的对齐由原来的8变为4,而a的对齐仍然是1。U3的对齐由8变为4,大小为12。
所以文章开头题目的答案为:16字节
结构体指针大小在32位系统下为4字节,64位系统下为8字节。
Acknowledgements:
http://www.cnblogs.com/wangguchangqing/p/4853438.html
http://www.cppblog.com/iuranus/archive/2009/01/06/71388.html
http://blog.csdn.net/yuucyf/article/details/7872502
http://blog.csdn.net/csw_100/article/details/5495309
http://www.cnblogs.com/Daniel-G/archive/2012/12/02/2798520.html
http://blog.csdn.net/talentluke/article/details/6108557
2017.04.09
本人博客会根据个人知识升级情况进行补充修改