一、有关结构体的基本知识
这部分比较基础,而且与第二部分关系不大,可以选择跳过
1.结构体的声明
结构体声明的一般格式如下:
//struct tag
//{
// memberlist;
//}variablelist;
其中memberlist为成员列表,variablelist为变量列表,它们两个比较“灵活”,要求较少:
(1)C要求一个结构或一个联合至少包含一个成员
(2)variablelist也可以不写,迟一些再声明变量也可以,但是不要忘记写分号
结构体声明的要点主要在于是否包含结构体标签tag
下面是第一种演示代码,为最常见的声明方法:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
struct student
{
char name[20];
char sex[5];
int age[3];
}s1,s2,s3;
return 0;
}
上图的声明方法也等同于下图的声明方法
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
struct student
{
char name[20];
char sex[5];
int age[3];
};
struct student s1,s2,s3;//这些变量的类型为struct student
return 0;
}
从语法的角度分类:
项目 | 变量 |
---|---|
tag | student |
member | name,sex,age |
variable | s1,s2,s3 |
当然上面的结构体标签也可以不写,用这种方法声明出的变量类型被称为**“匿名结构体类型”**,下面是一段演示代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
struct
{
char name[20];
char sex[5];
int age[3];
}s4;
struct
{
char name[20];
char sex[5];
int age[3];
}s5,*p;
return 0;
}
需要注意的有两点:
(1)即使s4和s5的成员列表相同,它们也被视为两种不同的结构体类型;
(2)匿名结构体类型的变量只能在声名的时候“使用”一次
举个例子,如果我在代码后面新增一条语句:
*p=&s4;
这条语句是错误的,一方面是它不能“修改”,另一方面是因为*p与s4的类型不同
2.结构体变量的初始化和定义
我们对上述代码做一些小修改,得到下面这三种初始化写法:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
//声明的同时对s1进行初始化:
struct student
{
char name[20];
char sex[5];
int age[3];
}s1 = { "柴浩","男",27};//默认的初始化顺序就是成员列表中的变量顺序
//对新变量s2的初始化
struct student s2 = { "野兽先辈","男",24 };
//不想按默认顺序初始化的话像下面这样写
struct student s3 = { .sex = "男",.name = "关瑞生",.age = "41" };
return 0;
}
如果嫌麻烦的话也可以利用typedef像下面这样写:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
//声明的同时对s1进行初始化:
typedef struct student
{
char name[20];
char sex[5];
int age[3];
}student;
student s1 = { "柴浩","男",27 };
//对新变量s2的初始化
student s2 = { "野兽先辈","男",24 };
//不想按默认顺序初始化的话像下面这样写
student s3 = { .sex = "男",.name = "关瑞生",.age = "41" };
return 0;
}
但实际上也有一种不建议使用typedef的声音,原因在于大工程中不易快速辨认s1这样的变量究竟是什么类型的
除此之外,这种写法可能对结构体的自引用产生一点语法方面的误导,下面是正常的结构体自引用写法:
struct stu
{
int memberlist;
struct stu*next;
}s1;
typedef以后的写法:
typedef struct stu
{
int memberlist;
struct stu*next;
}s1;
二、结构体内存对齐
1.规则
(1) 第一个成员存放在“与结构体变量的偏移量为0”的地址处
(2)对齐数=“编译器默认的一个对齐数”和“该成员大小”的较小值,vs的默认对齐数为8
(3)其他的成员要对齐到对齐数的整数倍的地址处,也就是说下一个变量要从"符合上 述条件的地址"开始存放
(4)结构体总大小为最大对齐数的整数倍(每一个成员都有一个对齐数)
(5)当结构体嵌套了结构体时,嵌套的结构体的对齐数就是这个结构体的最大对齐数的整数倍,此时何以暂且忽略第二条;
先来一段演示代码:
struct S1
{
char c1;
int i;
char c2;
};
对于这个结构体,c1从0开始对齐到1,i从4对齐到7,c2从8开始对齐到9,其中最大对齐数为4,所以这个结构体的大小就是12
下面是示意图:
其中一个小方块代表一个字节,红色代表未利用的字节
如果我们换一个顺序:
struct S1
{
char c1;
char c2;
int i;
};
内存中的情况变成了下面这样:
此时这个结构体就只占用8个字节了
2.意义
(1)平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的惹妞以数据,某些硬件平台只能在某些 地址处取某些特定类型的数据,否则抛出硬件异常
(2)性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐,原因在于:为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存仅需要一次访问
如果想要尽可能节省内存的话可以把占用内存较小的变量放在一起声明
总体来说,结构体的内存对齐时拿空间来换取时间的做法
修正关于嵌套情况下结构体内存对齐规则的错误
2022.10.13