关于结构体,或许你想知道的都在这里

本文详细介绍了C语言中的结构体,包括如何声明和使用结构体,以及结构体的内存对齐规则。文章通过实例解析了结构体声明的两种方式,强调了结构体标签的重要性,并探讨了结构体自引用和嵌套结构体的初始化。此外,还解释了内存对齐的原因和规则,帮助读者深入理解结构体在内存中的布局和性能影响。
摘要由CSDN通过智能技术生成

  疯狂还债中。

  本篇内容主要在于讲解结构体的知识。

  1.1 结构体长啥样

  我们都知道结构体是多种数据类型的集合,具体要包含什么样的数据类型需要根据具体问题而定,而结构体一般长这样。

struct tag
{
member-list;
}variable-list;

  那么既然我们在解决实际问题的时候要使用到结构体,那么我们势必无法绕开以下问题。

  结构体如何正确声明?

  结构体如何使用?

  当然,了解了这两个问题后只是对结构体有了一个比较初步认识,在本文我们还会讲解关于结构体内存对齐的内容,以便各位读者对于结构体有更深层次的了解。

   下面我们来探究第一个问题,结构体如何正确声明。

  1. 结构体的声明

  现在,我想用结构体来定义一个学生,结构体里面要包含学生的姓名,年龄,性别和学号这四个内容,那么我们应该如何使用结构体呢?

struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢

  我想,这样去声明这样一个结构体就没有问题了。但是事实上结构体的声明还有另外一种特殊的方法,即省略tag(Stu)的声明方法,这种特殊的命名方法我们称为不完全声明。

  但是有一个小问题,在指针进阶内容中,如果两个不同的指针指向了相同内容的字符串,那么在VS2019编译器的环境下,这两个指针是相等的。

  那么对于结构体,有没有类似的结论存在呢?

  我们来看看下面两个代码。

  

struct
{
    int a;
    char b;
    float c;
}x;

struct
{
    int a;
    char b;
    float c;
}a[20], *p;

p = &x; //这样写代码可以嘛???

  我们定义了两个在内容上一模一样的结构体,同时结构体变量p和x也都进行了声明,那么p = &x这样一穿代码是否是正确的呢?

  答案是否定的,编译器会报错。由于结构体缺少了tag标签,虽然这两个结构体是有着一模一样的内容,但是在编译器的眼中这就是两个不一样的结构体,对应类型的结构体指针才能接收对应类型的结构体,所以这么写代码的是不好的。在C语言中,我们还是要养成良好的代码风格,带上标签也不费什么事儿,你说对吧。

  既然我们已经知道了结构体如何声明,那么使用结构体就成了我们下一个需要解决的问题。

   2. 结构体如何使用

    2.1 结构体变量赋值

struct Point
{
    int x;
    int y;
}p1; //声明类型的同时定义变量p1

struct Point p2; //定义结构体变量p2
               
struct Point p3 = {4, 5}; //初始化:定义变量的同时赋初值。

struct Stu //类型声明
{
    char name[15];//名字
    int age; //年龄
};

struct Stu s = {"zhangsan", 20};//初始化

  我们看完了结构体变量初始化和赋值之后,不免得想到了一个问题,那就是结构体里面可以套用他自己嘛?结构体里面是否能含有另外一个结构体呢?

  关于第一个问题,答案是可以的,但是需要明白怎么写。

  结构体当然可以套用他自己,这种操作我们称为结构体自引用,我们可以用结构体来模拟实现数据结构中的链表。链表中的结点由数据部分和地址部分组成,数据部分存放应有的数据,地址部分存放下一个节点的地址。明白了这一点,我们来看看这个代码是否正确。

struct Node
{
int data;
struct Node next;
};

  我们稍微动点脑筋思考一下,这个代码一看就是错误的。如果这个代码正确,那么在求这个结构体所占内存空间大小的时候只会出现一种情况,那就是根本停不下来。因为结构体里是他自己,相当于函数递归没有限制条件从而导致不停的递归。

  那么,怎么样进行正确的自引用呢?我们再思考链表里结点的构成,就知道了,正确写法应该是这么写。

struct Node
{
int data;
struct Node* next;
};

  那么,关于结构体中是否能含有另一个结构体,又怎么进行初始化,咱们看看下面的代码就知道了。

struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {4, 5};
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//

  到此两个大问题就解决了,现在我们还有一个问题需要解决,结构体中存在一种现象叫做内存对齐,这是怎么一回事呢。

  3.结构体的内存对齐

  这个代码的结果是什么呢?是单纯的8+1+4嘛?

struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S1));

  如果你打开vs并运行这一串代码,你会发现答案出乎你的想象,这个代码最终的答案是16,而不是13,那出现这种情况的原因是什么呢?

  我们先来介绍一下结构体内存对齐的规则。

 结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
     对齐数 = 编译器默认的一个对齐数该成员大小的较小值
                                        VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

   我们画个图,就能理解这个对齐规则的意思了。

  

  我们来分析一下,既然结构体的第一个元素是double型,它自己的大小是8个字节,vs默认对齐数也是8字节,所以偏移量就是8个字节,所以从0开始,一直存到地址为7的地方,就结束。

  接下来是c ,c自身大小是1字节,vs对齐数是8字节,所以偏移量就是1字节,正好8是1的倍数,所以地址为8的地方就存放了c。

  最后的i,由于地址为9的内存不是4的倍数(为什么是4的倍数参照上面的分析),所以现在要浪费内存,直到到地址为12的地方,12是4的倍数,所以开始存放,一直存到15,此时结构体大小是16字节,S3结构体的最大对齐数就是8,而16正好是8的整数倍,所以结构体S3的大小就是16字节。

  至于结构体里嵌套结构体按照上面的分析来就好,一层一层求即可。

  但是很奇怪,为什么会有这种情况出现呢?

 为什么存在内存对齐?
大部分的参考资料都是如是说的:
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则会有硬件异常的问题。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说:
结构体的内存对齐是拿空间来换取时间的做法。

  大家也不必要过多的纠结,结构体的内存对齐是客观存在的现象,大家只需了解,不需要过分纠结。

  那么本篇文章到这里就结束了,如果本篇文章帮到了你不妨给作者点一个免费的赞,如果能点一个关注那就更好了。你们的支持是支撑作者更新的最大动力!

  感谢阅读

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值