这是我刚学结构体与数据内存分配时经常出错的知识,想要写出来给初学者或是忘记该知识点的人提个醒~
1、结构体的内存对齐问题
对于结构体的每一个成员变量,会根据不同类型而产生不同的对齐方式
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)整数倍偏移量的地址处。
其中,对齐数=编译器默认对齐数 与 该成员变量字节大小 的较小值。
3.结构体总大小为最大对齐数的整数倍。(注意:每个成员变量都有一个自己的对齐数)
4.对于结构体嵌套的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体整体大小要是所有对齐数中(包含嵌套结构体对齐数)最大对齐数的整数倍。
下面是个小例题:(以下列题均在vs编译器上编码,其默认对齐数为8)
struct s
{
double d;
char c;
int i;
};
printf("%d\n',sizeof(struct s));
其中double类型的d对齐数为8,而它又是第一个结构体成员变量,所以在与结构体变量偏移量为0的地址处开始储存。char类型的对齐数为1(因为默认的8与char的字节大小1比较,较小值为1,所以其最大对齐数为1。int类型也是如此),所以c存储在起始地址为8(第9个位置上)而int型的i对齐数为4,根据第2点可知,i要存储在12为起始地址的存储空间上。此时整个结构体的内存大小为8+1+3(浪费的结构体空间)+4=16,恰好是最大对齐数8的整数倍,所以该结构体的内存大小为16
这里给大家穿插一个修改默认对齐数的方法:
#pragma pack (4) //即将编译器的最大对齐数设置为4
... //结构体
#pragma pack () //取消设置的最大对齐数
2、位段在内存中的储存问题
首先先说一说位段是个什么东西,位段的声明和结构体是相似的,有两点不同:
1.位段成员必须是整形(int ,signed int ,unsigned int ,char ...)
2.位段的成员名后有一个冒号和一个数字
struct s
{
int a : 2 ; //两个比特位
int b : 5 ; //五个比特位
};
关于位段的内存分配:
1.位段的空间是按照以4个字节(int)或1个字节(char)的方式开辟的。
2.需要知道位段涉及很多不确定因素,是不跨平台的,可移植程序应避免使用位段。
1)、位段中最大设置比特位数不确定(16位机器最大16,32位机器最大32);
2)、位段中的成员在内存中从左向右分配,还是从右向左分配没有标准;
3)、当第二个位段成员较大无法容纳于第一个位段成员剩余位时,是舍弃还是利用剩余位。
优点: 跟结构相比,相同效果的同时更好的节省空间。但存在跨平台问题。
下面是一个例题:(对于vs来说内存是从左向右存储,剩余位舍弃)
struct S
{ //因为是char型所以一个字节一个字节开辟内存空间
char a : 3 ;
char b : 4 ;
char c : 5 ;
char d : 4 ;
};
int main
{
struct S a={0};
a.a = 10; //二进制表示:1010 存3位:010
a.b = 20; //二进制表示:10100 存4位:0100
a.c = 3; //二进制表示:011 存5位:00011
a.d = 4; //二进制表示:100 存4位:0100
return 0;
}
先开辟一个字节(8位):00 00 00 00
存a: 00 00 00 10
存b: 00 10 00 10 //这里最左边剩下的一位因存不下c而舍弃
开辟第二个字节 : 00 00 00 00
存c: 00 00 00 11 //最左边剩下的三位存不下d而舍弃
开辟第三个字节 : 00 00 00 00
存d: 00 00 01 00
三个字节的储存内容为:0010 0010 0000 0011 0000 0100
转换成16进制在内存中是:OX 2 2 0 3 0 4
3、枚举类型
enum Sex //枚举的定义
{
MALE, //默认值为0 (可修改默认值 eg:MALE = 3, )
FEMALE, //默认值为1
};
enum Sex s = MALE; //枚举的使用
可以使用#define定义常量,但枚举更有优点:
1.增加代码的可读性和可维护性;
2.与#define定义的标识符相比,枚举存在类型检查,更加严谨;
3.使用枚举可以有效防止命名冲突
4.便于调试(#define定义的符号在调试时内容被替换,导致执行内容与看到的内容不符)
4、联合体(共用体)的内存分配:(同样存在默认对齐数 vs编译器为8 )
*联合体成员是共用同一块内存空间的,联合体变量大小至少是最大成员变量的大小,同时要是最大对齐数的整数倍(数组的对齐数是元素的对齐数)
union Un
{ //联合体的声明
int i;
char c;
}; //同一时刻i和c不可同时使用
union Un a; //对于联合体变量a存在:&a==&(a.i)==&(a.c)
所以联合体变量a的大小为8
通过联合体我们可以来检验当前机器的存储模式:
int check_sys()
{
union
{
char c; //这里的u是一个匿名结构体类型(创建后只能使用一次)
int i;
}u;
u.i=1;
return u.c; //返回1表示为小端储存,返回0表示为大端储存
}
因为联合体u中的char类型的c与int类型的i是共用一块4字节的内存空间的,所以如果是小端储存模式那么char类型的c刚好读取到存在i中的1,反之大端储存模式则读出0。
好啦,今天就写到这儿吧,希望能帮大家更好的理解结构体在内存中的储存方式。若以上内容有什么错误请大家及时纠正我,我会及时改正的。(谢谢大家ヾ(≧▽≦*)o)