我们在刚开始学习结构体时总觉得它的大小就是所有元素的和加起来即可,但实际上并不是这么简单,通常情况下要考虑字节对齐问题,结构体各成员之间或结构体的尾部需要添补一些空白字节。包含相同成员的结构体,如果成员声明的顺序不同,其占用的内存空间也可能不同。尽管在程序中可以利用sizeof获取当前编译条件下结构体所占内存空间的大小,但深入了解结构体中成员对齐所遵循的规则也很有必要。
首先我们来说一下字节对齐的规则,结构体成员的对齐是指成员相对于结构体首地址的偏移量应该是2^n的倍数(为什么要说是2^n的倍数呢,因为我们内置类型元素的字节肯定都是2^n字节的,所以我们就说以2^n倍数对齐)。如果是以1-byte对齐时,这个成员的偏移量就很随意了0,1,2,3,4。。。。假设2-byte对齐时,那么成员偏移量只能是0、2、4……而4-byte对齐的时候偏移量只能是0、4、8……
我们结构体中各成员以什么方式对齐与成员的大小有关,成员大小为a则进行a-byte对齐,即成员的偏移量应该被成员的大小整除。例如int类型的成员其偏移量必须是4的整数倍,double类型的成员其偏移量必须是8的整数倍。
我们给大家举个例子:
#include<stdio.h>
struct s1
{
char a; //offset 0
double b; //offset 8
}x1;
struct s2
{
double a; //offset 0
char b; //offset 8
short c; //offset 10
}x2;
int main()
{
printf("%d\n",sizeof(x1)); //16
printf("%d\n",sizeof(x2)); //16
return 0;
}
s1结构体a以1--byte对齐,偏移量为0即可,b是double型,偏移量只能是8,所以需要在a后加上7个字节的空格,sizeof(x1)即为16.同理,s2中a是double型以8-byte对齐,偏移量为0即可,b是char类型,所以紧接a后,所以偏移量是8,c是short类型,按照2-byte对齐,所以偏移量是10,所以b和c之间应该补上一个字节空格,c之后也应补上4个字节的空格,sizeof(x2)即为16.
如下图:
在VC/VS的编译器中,通过#pragma pack可以规定对齐的字节。例如在定义结构体之前添加#pragma pack(n),则编译器为某个成员将其对齐数设置为n,这里n的取值可以是1、2、4、8、16,如果不是这些数,就按上文的方式处理对齐。举个例子:
#include<stdio.h>
#pragma pack(4)
struct s
{
short m1; //0ffset 0
double m2; //offset 4
}x;
int main()
{
printf("%d\n",sizeof(x));
return 0;
}
这个输出结果是12,并不是我们之前计算的16,因为这时我们是以4个字节对齐,m1后只是补全了2个字节的空格。
我们将pack改为1或者2,答案是10,这个时候并没有补空格,全部占满。
我们再将pack改为3/5.。。,答案是16,事实证明这时候并没有采取这个对齐方式,而是默认的方式。
嵌套结构体的大小
先上代码:
#include <stdio.h>
#define field_offset(s,f) (int)(&(((struct s *)(0))->f))
struct ss1
{
int a; //0
double b; //8
struct aa //16
{
int bb; //16
double cc; //24
}aa;
int c; //32
}ss1;
struct ss2
{
int a; //0
double b; //8
struct aa
{
int bb;
double cc;
};
int c; //16
}ss2;
int main()
{
printf("%d\n",field_offset(ss1,c)); //32
printf("%d\n",field_offset(ss2,c)); //16
printf("%d\n",sizeof(ss1)); //40
printf("%d\n",sizeof(ss2)); //24
}
先看这个,这两个唯一的区别就是是否定义了结构体变量,从结果可以看出, 未定义结构体变量的结构体大小比较小,因为内部的结构体并不计算(内部为联合体也是一致)。从这个结构体嵌套中我们可以看出它采用的对齐方式是采取的是结构体中的内置类型的最大元素的字节数,而不是用内部结构体的大小当作对齐字节,这是很重要的一点。
内部如果是联合体,和上面基本一致,共用体定义变量的情况下采用的是共用体中字节数最大的元素字节,共用体中所有的变量的起始偏移都是一致的,这是与内部是结构体不同的一点。
struct ss1
{
int a; //0
double b; //8
union aa //16
{
int bb; //16
double cc; //16
}aa;
int c; //24
}ss1;
printf("%d\n",field_offset(ss1,aa.cc)); //16
printf("%d\n",sizeof(ss1)); //32