结构体用来保存不用类型的数据非常方便,但是计算结构体的大小并不是将变量所占的字节数相加,而是按照一定的对齐规则进行计算结构体的大小的。
由于地址对齐的原因,结构体大小的计算必须满足两条原则:
一、结构体每一个成员相对于结构体首地址的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
二、结构体的总大小必须是所有成员大小的整数倍(数组成员除外,结构体成员除外)
下面通过代码来验证
1、一般的结构体
#include<stdio.h>
#include<stdlib.h>
struct Data{
char c1; //1
char c2; //1
int i; //4 因为偏移量要等于当前成员大小的整数倍,2不是4的整数倍
//所以2后面要补齐2,所以是1+1+2+4个字节
};
int main()
{
printf("结构体大小为%d\n",sizeof(struct Data));
system("pause");
return 0;
}
这个结构体大小可以按照对齐规则中的第一条就可以计算
在看下面一个结构体大小该等于多少呢?
#include<stdio.h>
#include<stdlib.h>
struct Data{
char c1; //1
int i; //4 + 3
char c2; //1 因为偏移量8是1的整数倍,结构体大小为1+4+3+1 = 9
//但是不是所有成员的整数倍,所有要继续向后偏移,所以是12
};
int main()
{
printf("结构体大小为%d\n",sizeof(struct Data));
system("pause");
return 0;
}
计算这个结构体的大小如果只按结构体对齐原则的第一条,结构体大小就应该为9(9 = 1+4+3+1,3为补齐的字节数),但是结构体的大小为12,那是因为还要满足结构体对齐原则中的第二条,结构体总大小为所有成员大小的整数倍,(9不是4的整数倍),所以要继续向后偏移,直到达到所有成员的整数倍为止。
2、成员包含数组的结构体
#include<stdio.h>
#include<stdlib.h>
struct Data{
char c; //1
int i; //4+3
char str[10]; //10 18不是总的整数倍,所以继续偏移到20
};
int main()
{
printf("结构体大小为%d\n",sizeof(struct Data));
system("pause");
return 0;
}
这个char类型的数组,只需要把它看做十个char连在一起就可以了,加起来就是18,在满足结构体总大小为成员整数倍,(18不是10的整数倍),所以大小就是20
那为什么结构体的总大小必须是所有成员大小的整数倍时数组要除外呢?
#include<stdio.h>
#include<stdlib.h>
struct Data{
char c; //1
int i; //4+3
char str[12]; //12 1+4+3+12 = 20,但是不是12的整数倍
}; //所以继续偏移到24
int main()
{
printf("结构体大小为%d\n",sizeof(struct Data));
system("pause");
return 0;
}
可以看到当数组大小改为12时,结构体大小还是为20,并不是上面计算出的24,那是因为数组不满足结构体总大小为所以成员的整数倍,如果满足的话就是(1+4+3+12 = 20)但是不是12的整数倍,所以继续偏移到24,但是结构体的真实大小为20。
3、成员包含结构体的结构体
#include<stdio.h>
#include<stdlib.h>
struct Data{
char c; //1
int i; //4 + 3
struct s{ //如果只是声明结构体,那么算大小就会忽略结构体,不占空间,变量定义了才占空间
char c1; //1
int j; //4 + 3
};
float f; //16 + 4 = 20,但是不是结构体8的整数倍,如果按照总大小为结构体成员的整数倍的话就是24
}; //但是运算结果为20,所以当成员是结构体时也不遵循原则二
int main()
{
printf("结构体大小为%d\n",sizeof(struct Data));
system("pause");
return 0;
}
当只是声明结构体,不定义的话运行结果如下:
当声明了结构体,同时定义了结构体时
struct Data{
char c;
int i;
struct s{ //如果只是声明结构体,那么算大小就会忽略结构体,不占空间,变量定义了才占空间
char c1; //不同编译器会不一样
int j;
}tmp; //定义结构体变量tmp
float f;
};
运行结果:
可以看到,如果只是声明结构体,那么算结构体大小时就会忽略结构体,结构体不占空间,要定义了结构体变量才占内存空间,还应该注意,在这里对于不同的编译器会出现不一样的结果,有的编译器在没有定义变量的时候结构体大小也是20,在定义了结构体大小的时候也是20,有的编译器在没有定义结构体变量时就会忽略结构体。
在这里,里面这个结构体的大小是8,那么结构体大小是否就要向8对齐呢?这个结构体的大小是20,很明显不是8的倍数,(所以原则二中要去掉结构体),所以计算结构体大小时,是把里面这个结构体就单独的看做是一个char,和一个int,不是看做一个整体。
4、成员包含联合体的结构体
#include<stdio.h>
#include<stdlib.h>
struct Data{ //1+4+3+4 = 12
char ch; //1
int i; //4 + 3
union{ //联合体大小就是成员中最大类型的大小
char ch1;
int j; //4
};
};
int main()
{
printf("结构体大小为%d\n",sizeof(struct Data));
system("pause");
return 0;
}
在联合体前面的大小为8,因为联合体的大小就是联合体成员中最大类型的大小,这个联合体最大类型为 int ,4个字节,(8是4的整数倍)所以这个结构体大小是8+4 = 12。
5、指定对齐值
a:对齐值小于最大类型成员值
#include<stdio.h>
#include<stdlib.h>
#pragma pack(4) //指定向4对齐 最大是8
struct Data{
char c; //1
int i; //4 + 3
float f; //4 到这里是12按照4对齐的话12是4的倍数,所以就是12+8 = 20
//如果按照8对齐的话12不是8的倍数,所以要偏移到16,最后大小等于24
double d; //12 + 4 + 8 = 24
};
int main()
{
printf("结构体大小为%d\n",sizeof(struct Data));
system("pause");
return 0;
}
这里是指定向4对齐,按照4对齐的话在double这一段字节数应该为1+4+3+4 = 12,是4的倍数,在加上double的大小,所以总大小就是12+8 = 20 , 如果是按照8对齐的话,到double前面总大小应该为1+4+3+4 = 12,因为12不是8的整数倍,所以还要向后偏移到16,所以总大小应该为12+4+8 = 24 。
如果去掉 #pragma pack(4) 这一句,结构体就会向 8 对齐,结果就等于24。
b : 对齐值大于最大类型成员值
#include<stdio.h>
#include<stdlib.h>
#pragma pack(10) //当成员的大小超过了pack规定的大小对齐,那么就按规定的大小对齐
//如果最大成员的大小没有超过要求的对齐的话,那还是按照最大成员来对齐
struct Data{
char c; //1
int i; //4 + 3
float f; //4
double d; //12 + 4 + 8 = 24
};
int main()
{
printf("结构体大小为%d\n",sizeof(struct Data));
system("pause");
return 0;
}
在这里指定的对齐值是向10对齐,那是否就向10对齐呢?从运行结果可以看到不是的,如果按照10对齐的话,结构体的大小应该为30才对,这里结果等于24明显是按照8对齐的。
也就是说当成员的大小超过了pack规定的大小对齐时,那么就按规定的大小对齐,如果最大成员的大小没有超过要求的对齐的话,那还是按照最大成员来对齐。