【C语言】结构体+内存对齐+位段

一、结构体是什么

结构是一些值的集合。

这些值被称为成员。

每个成员可以是不同类型的变量。

二、结构体类型的声明

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

上面代码声明了一个描述学生的结构体类型。

这个类型的标签是Stu,整个结构体的类型是struct Stu。

声明了结构体的类型之后,我们就可以定义结构体变量了。

下面定义一个struct Stu结构体类型的变量,变量名为stu。

struct Stu stu;

事实上,我们可以在声明结构体类型的同时定义结构体变量。

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

三、结构体的自引用

提问:结构体中能否包含类型为该结构体本身的成员?

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

答:不可以。

因为C语言中,结构体所占内存必须在编译时确定。

如果一个结构体中含有它本身,这个结构体所占内存大小会循环成一个无法计算的数值。

正确的自引用方式

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

考你一下:

上面代码的结构体类型是什么?

没错,就是struct Node。

我们不难发现,结构体类型的名称较为冗长。

那有没有比较简便的表示方式呢?

我们可以利用typedef给结构体类型取别名:

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

上面代码在声明结构体的同时,给结构体类型取了别名叫Node。

注意,此时 } 后面的Node并不是定义的结构体变量,而是typedef给struct Node类型取的别名。

四、结构体变量的定义和初始化

如何定义变量:

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

struct Point p2; //定义结构体变量p2

初始化:定义变量的同时赋初值

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

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

五、结构体传参

记住:结构体传参,要传结构体的地址。

六、结构体内存对齐

看完上文,我们已经掌握了结构体的基本使用。

现在我们深入讨论一个问题:计算结构体的大小。

先来掌握结构体的内存对齐规则

1. 第一个成员在结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到对齐数整数倍的地址处。

对齐数 = 编译器默认对齐数与该成员大小相比的较小值。(VS中默认为8)

3. 结构体总大小要是最大对齐数的整数倍。

最大对齐数:结构体所有成员的对齐数中最大的那个。

4. 如果嵌套了结构体:

  • 嵌套的结构体对齐到自己的最大对齐数的整数倍处。

  • 结构体的总大小要是所有最大对齐数的整数倍。

下面来看几个例子:

【C语言】结构体+内存对齐+位段_第1张图片

如图所示,c1的对齐数是1,作为第一个成员,对齐到偏移量为0的地址处。

下面简写,不再重述偏移量为x的地址处,用数字替代

i的对齐数是4,对齐到4的整数倍,也就是4处,占用4、5、6、7四个字节。

c2的对齐数是1,对齐到1的整数倍,也就是8处。

此时已占用9个字节。

而结构体总大小要是最大对齐数的整数倍,此例中最大对齐数是i的对齐数4。

故总共需占用12个字节。

理解了上面,让我们再看几个例子:

【C语言】结构体+内存对齐+位段_第2张图片

如图,c1的对齐数是1,作为第一个成员,对齐到0处。

c2的对齐数是1,对齐到1的整数倍,也就是1处。

i的对齐数是4,对齐到4的整数倍,也就是4处,占4、5、6、7四个字节。

此时总共占用了8个字节,刚好是最大对齐数4的整数倍。

故此结构体大小为8。

再来看:

【C语言】结构体+内存对齐+位段_第3张图片

理解了前面两个例子,相信你很快就能看懂本例的含义。

那么,为什么会存在内存对齐呢?

1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特
定类型的数据。
2. 性能原因:
访问未对齐的内存,处理器要访问两次;而访问对齐的内存仅需访问一次。

总体来说,结构体的内存对齐是以空间换时间的做法

了解完结构体占用内存的规则后,如果想要节省空间,我们应该如何设计结构体

答:让占用空间小的成员尽量集中在一起。

此处读者可以自行验证。

另外,我们可以使用#pragma 这个预处理指令,来改变默认对齐数

#include 
#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

C语言还提供了一个宏,可以计算结构体中某变量相对于首地址的偏移。

offsetof宏:

【C语言】结构体+内存对齐+位段_第4张图片

七、结构体实现位段

讲完了结构体,就不得不讲讲结构体实现位段的能力。

什么是位段

C语言允许,一个结构体以比特位为单位,指定成员所占内存大小

这种以位为单位的成员,就称为位段

位段的声明和结构是类似的,但是有两个不同:

1.位段的成员类型一般情况下都是相同的,且必须是整型家族(int、char等)。

2.位段的成员名后边有一个冒号和一个数字,表示指定的内存大小。

比如:

struct A
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
};

上述代码中,A就是一个位段类型。

冒号后面的数字表示需要的比特位个数(1字节=8比特位)。

位段类型的大小如何计算:

struct S
{
    char a:3;
    char b:4;
    char c:5;
    char d:4;
};

struct S s = {0};

s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

先根据成员类型开辟空间

  • 如果是int则先开辟4个字节(32个比特位)。

  • 如果是char则先开辟1个字节(8个比特位)。

  • 先开辟一次,后面不够再开辟,每次开辟都如此。

将a、b、c、d的值从十进制转换为二进制(研究比特位)。

再根据位段声明中,成员指定的比特位数,对变量的二进制值进行截断。

再将其放入内存的字节中,丢弃多余的部分。

内存不够了再重新开辟,本例中总共开辟了3次,也就是3个字节。

【C语言】结构体+内存对齐+位段_第5张图片

我们将刚刚内存中存储的数据,从二进制转换成十六进制。

利用VS进行调试监视,来验证我们的模拟:

【C语言】结构体+内存对齐+位段_第6张图片

不难看出,在本例中,位段的内存分配确实如此。

因此,与结构相比,位段可以很好地节省空间。

但是,位段有很多不确定的因素:

1. int 位段被当成有符号数还是无符号数是不确定的。

2. 位段在内存中从左向右分配,还是从右向左分配,标准尚未定义。

3. 当一个结构包含两个位段,第二个位段较大,无法容纳于第一个位段剩余的位时,是

舍弃剩余的位还是利用,这是不确定的。

因此,位段是不跨平台的,注重可移植的程序应该避免使用位段。

你可能感兴趣的:(C语言重点突破,c语言,开发语言)