大家好,我是KookNut39,在这里分享我所学的一些知识,希望可以帮助你进步,更希望能成为你生活中的朋友。大家如果遇到了任何的问题,都可以找我倾诉,我有空的时候,一定及时回复大家,愿大家心中有梦,眼中有光,一往无前!
前言:今天有一个粉丝问我,结构体是什么?为什么要用结构体?这样的问题对于一个萌新来说是再正常不过的事情,但是我很高兴,其中的原因就是:他会真正的思考问题,会提出自己的问题和看法,而我也确实愿意为各位初学者去力所能及的解决问题,毕竟我也是这么过来的,也算是让自己的人生能够更加有意义一些吧!
ok,让我们首先来解决他的疑惑!首先来说结构体是什么?结构体,他肯定是一个数据结构,而我们把单独的一些数据元素整合在一起,就形成了结构体!那为什么要有结构体呢?结构体是为了方便我们管理那些有联系的数据啊!举个例子,就拿学生管理系统来说吧,每一个学生最起码有的就是他的学号、姓名、专业、学院等等吧,这4个类型的数据,都属于同一个人,如果我们想根据学生的姓名查学号,查专业等,怎么操作才方便呢?如果把这些数据放在结构体中,那是不是就搞定了?结构体把这些数据整合在一起,对于程序员,我们就方便去管理了!这位粉丝还提到了类,类是C++中才有的,所以对于C语言程序员来说,那结构体就是他们的 “类” 啊!!
先来看个结构体声明:
typedef struct _STUDENT_INFO_
{
char Name[20];//学生姓名
int age;//年龄
}STUDENT_INFO, *PSTUDENT_INFO;
复制代码
这个结构体的声明方式可能和大家在书本中看到的不一样,但是这个和个人的编程习惯有关系,我习惯了这样写,解释一下这样写是为什么,以及各个部分各自代表什么。首先映入我们眼帘的是两个关键字,分别是typedef 和 struct,用这两个关键字来定义结构体,被typedef标记过,可以理解为这个结构体就成为一种类型了吧,类似于int。此时_STUDENT_INFO_是结构体的名称,但是我们一般不使用他,为了避免出现struct _STUDENT_INFO_这种繁琐的声明方式。真正使用的是STUDENT_INFO,相当于我们平时使用的int这些类型一样,还有逗号隔开的PSTUDENT_INFO是结构体的指针,举个例子:
STUDENT_INFO Stu2 = { 0 };//Stu2是一个结构体对象
PSTUDENT_INFO Stu_ptr = NULL;//Stu_ptr是一个结构体指针,将来指向一个结构体
复制代码
就像上面这样,我们书写的时候就方便了很多,直接就写个结构体的名称就可以声明变量了,不用像你们课本中教的那样,每次声明变量都得写那个struct关键字,类似这样:
struct _STUDENT_INFO_ Stu1 = {0};//没有必要每次这样去写
复制代码
我们都知道指针和普通变量访问结构体中的成员时候方式是不同的:
memcpy(Stu2.Name, "Tony", 4);//设置学生姓名
Stu2.age = 10;//设置学生年龄
Stu_ptr = &Stu2;//对Stu2取地址操作,使Stu_ptr指向Stu2这个结构体
Stu_ptr->age = 12;//修改学生年龄
复制代码
对于普通的成员来说,它是通过点运算符来访问成员的,指针是通过->来访问成员的,这是他们访问方式的不同。
C语言或者C++内存粒度对齐是我们经常提到的问题,而对内存的有效合理的利用,必然会使我们写出来的代码更加高效。之前写代码过程中出现了一个bug,就是由于内存粒度原因导致,所以总结一下。
首先我们来看一下理论知识(我个人理解的):
结构体中数据成员内存对齐的原则:按照一个结构体中的成员中最大字节数整数倍对齐,比如在x86下,出现int(4字节) char(1字节) double(8字节),则肯定是按照最大的8字节整数倍对齐,8字节就相当于一个基准一样,如果遇到的字节数可以在8字节内存放,那就ok,否则需要重新申请8字节来存放,至于说是8的几倍,那需要看数据存放的顺序,内存满足多插少补的原则。
接下来我就结合代码和调试信息来说明内存粒度对齐的相关问题:
以下提到的测试数据,基于x86:
typedef struct _STRUCT_A_
{
char a;
int b;
char c;
}STRUCT_A;
复制代码
面对以上的一个结构体,初学者可能认为是1+4+1总共6个字节。那么我们来实际看看它的大小究竟是多少:
它是12字节的大小,那么我们再来看看它的内存分布情况:
确实初始化为12个字节的0,那我们字符赋值字符‘a’,int值5以及字符‘c’之后,内存中又是如何存放的呢?
那我们接下来就要思考如何优化这个结构体,让他它占有更少的内存呢?我们可以写成以下两种形式:
typedef struct _STRUCT_A_{ int b; char a; char c;}STRUCT_A;typedef struct _STRUCT_A_{ char a; char c; int b;}STRUCT_A;
复制代码
我们再来看看内存中的情况以及结构体大小为多少:
以上证实了我们一开始的说法,当char存放占4字节基准为1字节,接下来的成员还是char,拿它就可以继续存放,存放第二个char之后,还有2字节空余,但是遇到了4字节的int,不够了,只能存放在另外的4字节内存中了。
那我们有没有办法,就是不管我们怎么存放,让它都有固定的内存对齐粒度呢?当然是可以的:
#pragma pack(push)
#pragma pack(2)
typedef struct _STRUCT_A_
{
char a;
int b;
char c;
}STRUCT_A;
#pragma pack(pop)
复制代码
我们可以使用以上代码,使内存粒度对齐按照2字节对齐,我们来验证一下:
当然也可以按照1字节对齐,只需要修改pack中的值为1,如下:
#pragma pack(push)
#pragma pack(1)
typedef struct _STRUCT_A_
{
char a;
int b;
char c;
}STRUCT_A;#pragma pack(pop)
复制代码
但是我们在实际情况中,可能会遇到更加复杂点的情况,比如结构体的成员是结构体或者联合体。这种情况又是如何对齐的呢?我们来看一个稍微复杂一点的结构体:
typedef struct _STRUCT_A_
{
uint32_t a;
uint32_t b;
}STRUCT_A;
typedef union _UNION_A_
{
char* m1;
STRUCT_A m2;
}UNION_A;
typedef struct _STRUCT_B_
{
UNION_A a;
UNION_A b;
UNION_A c;
UNION_A d;
uint16_t e;//2字节
}STRUCT_B;
复制代码
按照我们的想法,那应该UNION_A所占内存大小为8字节,那么STRUCT_B应该按照8字节对齐,那么5个数据成员,大小应该使5*8=40字节,但是实际情况好像并不是这样:
怎么会出现36这个情况呢?原因出现在STRUCT_A中,虽然unionA的大小确实为8字节,但是其8字节的主要原因是STRUCT_A是8字节,那STRUCT_A中是两个4字节的成员,所以内存粒度对齐还是4字节,这样就不难理解最后的STRUCT_B为36字节了,其中前4个成员是32字节,最后一个是2字节,但是按照4字节对齐,所以是36字节。
初来乍到,如果文中有不足之处,还请大家指正!吟诗一句:“大鹏一日同风起,扶摇直上九万里”