结构体中字节对齐问题
为了提高CPU访问内存的效率,可能CPU在读取数据的时候回一次性读取4字节、或者2字节、8字节等大小的数据,所有编译器在把数据存放于内存的时候,会自动对齐。
1、字节对齐规则:
字节对齐有以下几种规律:
1.1以最大的成员占据的空间大小对齐
typedef struct
{
char a; /*1*/
char b; /*1 b后面会有两个空字节*/
int c; /*4*/
}test_1;
4字节对齐,占据8字节,内存分布为:
byte0 byte1 byte2 byte3
char char 空 空
int
1.2剩余的空间足够,后面的变量会往前填补
typedef struct
{
int i; /*4*/
char a; /*1*/
char b; /*1*/
short d; /*2*/
}test_2;
4字节对齐,占据8字节,内存分布为:
byte0 byte1 byte2 byte3
int
char char short
1.3根据变量占据的空间从大到小,依次对齐
typedef struct
{
double d; /*8*/
short s1; /*2+2*/
int i; /*4*/
short s2; /*2+6*/
}test_3;
8字节对齐,占据24字节,内存分布为:
byte0 byte1 byte2 byte3 byte4 byte5 byte6 byte7
double
short int
short
1.4包含其他结构体时,不会拿该结构体的总大小对齐
typedef struct
{
int a; /*4*/
char b; /*1*/
char c ; /*1*/
short d; /*2*/
int e; /*4*/
}test_4;
typedef struct
{
int aa; /*4*/
short bb; /*2+2*/
test_4 mm; /*12*/
}test_5;
结构体test_4总共占据12个字节,但在计算结构体test_5类型的大小时,并不会把m为整体看出一个整体的大小来计算对齐字节,而是拆开test_4结构体,依次拿test_4里面的成员存放于内存中;
#include<stdio.h>
typedef struct
{
char a; /*1*/
char b; /*1 b后面会有两个空字节*/
int c; /*4*/
}test_1;
typedef struct
{
int i; /*4*/
char a; /*1*/
char b; /*1*/
short d; /*2*/
}test_2;
typedef struct
{
double d; /*8*/
short s1; /*2+2*/
int i; /*4*/
short s2; /*2+6*/
}test_3;
typedef struct
{
int a; /*4*/
char b; /*1*/
char c ; /*1*/
short d; /*2*/
int e; /*4*/
}test_4;
typedef struct
{
int aa; /*4*/
short bb; /*2+2*/
test_4 mm; /*12*/
}test_5;
int
main(void)
{
printf("%d ",sizeof(test_1));/*程序输出结果:8*/
printf("%d ",sizeof(test_2));/*程序输出结果:8*/
printf("%d ",sizeof(test_3));/*程序输出结果:24*/
printf("%d ",sizeof(test_4));/*程序输出结果:12*/
printf("%d ",sizeof(test_5));/*程序输出结果:20*/
return 0;
}
/*程序输出结果:8 8 24 12 20*/
由于字节对齐特性的存在,可能会使得我们设计的结构体占据的空间会变大。这在某些场合是应该要避免的,如网络通讯,如无避免则会产生流量损失。
所以合理设计结构体,即合理排放其成员变量十分重要。
2. 去除编译器的字节对齐规则
解决字节对齐带来的空间损耗问题,一种方法就是不要使用字节对齐规则,即使其1字节对齐:
#include<stdio.h>
#pragma pack(1)
typedef struct
{
char a;
char b;
int c;
}test_1;
typedef struct
{
int i;
char a;
char b;
short d;
}test_2;
typedef struct
{
double d;
short s1;
int i;
short s2;
}test_3;
typedef struct
{
int a;
char b;
char c ;
short d;
int e;
}test_4;
typedef struct
{
int aa; /*4*/
short bb; /*2+2*/
test_4 mm; /*12*/
}test_5;
#pragma pack()
int
main(void)
{
printf("%d ",sizeof(test_1));
printf("%d ",sizeof(test_2));
printf("%d ",sizeof(test_3));
printf("%d ",sizeof(test_4));
printf("%d ",sizeof(test_5));
return 0;
}
/*程序输出结果:6 8 16 12 18 */
3. 结构体中的位域
位域只能是在结构体中使用,其作用是为了节省空间。
比如我们要在结构体中定义一个变量来表示小学生的年龄(一般是6到14岁),显然,用char类型来定义变量都大了,所以可以借助位域的功能来限制char的位数。
14(岁)转为二进制是0b1110,共占据5位,再加上符号位共5位,所以在描述一个学生信息的结构体中,其年龄可以声明为:
typedef struct _stu
{
char age : 4;
char* name;
}stu;
这样,当age等于二进制位超过4位的数值时,编译会产生溢出警告。限定了结构体成员的位域,那么其占据的空间也会随之改变:
typedef struct _stu
{
char age : 4;
char a : 2;
char b : 2;
}stu; //4bit + 2bit + 2bit = 8bit = 1BYTE
如上占据了1字节。当然,如果在最后再定义一个char v : 2;,那么其大小自然就是2字节了。虽然说限制位域确实节省了空间,但是它节省的是位级别的空间,十分小,如果连位都要考虑浪费问题,那就有点变态了。
#include<stdio.h>
char* pointer1;
short int* pointer2;
int* pointer3;
double* pointer4;
float* pointer5;
typedef struct
{
char age : 4;
char a : 2;
char b : 2;
}stu1; //4bit + 2bit + 2bit = 8bit = 1BYTE
typedef struct
{
char age : 4;
char a : 2;
char b : 2;
char v : 2;
}stu2; //4bit + 2bit + 2bit = 8bit = 1BYTE
typedef struct
{
char age : 4;
char type;
char * name;
}stu3;
int
main(void)
{
printf("%d ",sizeof(pointer1));
printf("%d ",sizeof(pointer2));
printf("%d ",sizeof(pointer3));
printf("%d ",sizeof(pointer4));
printf("%d ",sizeof(pointer5));
printf("\n");
printf("%d ",sizeof(stu1));
printf("%d ",sizeof(stu2));
printf("%d ",sizeof(stu3));
return 0;
}
/*
程序输出结果:
4 4 4 4 4
1 2 8
所以不管是什么类型的指针变量都只占4个字节
*/