一文带你走进结构体

​大家好,我是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字节。

       初来乍到,如果文中有不足之处,还请大家指正!吟诗一句:“大鹏一日同风起,扶摇直上九万里”

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享