🔥引言
书接上文,我们了解关于结构体的基本知识,这篇将深入剖析结构体中一个重要的知识点:内存对齐
关于内存对齐是属于热门面试话题,对此单独放在一篇来分享
🌈个人主页:是店小二呀
🌈C语言笔记专栏:C语言笔记
🌈C++笔记专栏: C++笔记
🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅
文章目录
- 一、结构体中内存对齐
- 1.1 对齐规则
- 1.2 内存对齐的意义
- 1.3 #pragma(预处理指令)
- 1.3.1 pragma相关介绍
- 1.3.2 #pragma pack(n)修改默认对齐数
- 二、结构体实现位段
- 2.1 位段的概念
- 2.2 位段的内存分配
- 2.3 位段的跨平台问题
- 2.4 位段的应用
一、结构体中内存对齐
1.1 对齐规则
-
结构体第一个成员变量对齐相对于结构体成员地址偏移量为0的位置上
-
其他成员变量需要对齐到对齐数的整数倍
-
结构体总大小为最大对齐数的正数倍
如果存在嵌套结构体的情况,嵌套结构体占用空间需要对齐自身最大对齐数的整数倍,同时在计算结构体总大小的时候,嵌套结构体的最大对齐数参与比较
【注意】:对齐数 == 编译器默认的一个对齐数与该成员变量大小的较小值
-
在vs环境下,系统默认对齐为8
-
在Linux中没有默认对齐数,对齐数就是成员自身的大小
通过题目熟练的掌握以上知识.
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));--12
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));--8
struct S4
{
char c1;
struct S2 s2;
double d;
};
printf("%d\n", sizeof(struct S2));--24
【说明】:数值代表的是结构体变量地址处的偏移量
1.2 内存对齐的意义
⼤部分的参考资料都是这样说的:
平台原因(移植原因):
- 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
性能原因:
- 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;对齐的内存访问仅需要⼀次访问。
假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法
通过上述的观察,不难看出。如果不存在内存对齐,需要执行两个内存访问(对象被分放在两块内存块),而内存对齐只需要进行一次。
对此在涉及结构体时,需要考虑满足对齐,又要节省空间。可以将占用空间小的成员尽量集中在一起
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i
};
S2 < S1
1.3 #pragma(预处理指令)
1.3.1 pragma相关介绍
- 用于指定计算机或操作系统特定的编译器功能
- 根据定义
pragma
指令是计算机或操作系统特定的,并且通常对于每个编译器而言都有所不同 - pragma指令可用于条件语句以提供新的预处理器功能,或为编译器提供实现所定义的信息,
1.3.2 #pragma pack(n)修改默认对齐数
#include <stdio.h>
#pragma pack(1)//设置默认对齐数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pacK()//取消默认对齐数,还原为默认对齐数
int main()
{
printf("%d\n",sizeof(struct S));
return 0;
}
推荐使用场景,在结构体进行内存对齐时,如果对于对齐方式不能达到预期,可以通过该指令更改默认对齐数
获得该成员变量的偏移量
这里需要使用一个函数offsetof()宏,该函数被声明在stddef.h
文件中,以下是函数offsetof()宏
size_t offsetof(type,member);
【宏定义】:
#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
如果想要了解更多,可以参考下这篇博客Offsetof宏详解-CSDN博客.这里只如何去使用Offsetof()宏计算出结构体某成员地址的偏移量。
#include <stdio.h>
#include <stddef.h>
struct S
{
char a;
int i;
};
int main()
{
printf("%d\n",offsetof(struct S,i));
//那么这里的结果就是就是4
return 0;
}
【小总结】:
结构体中的内存对齐是为了以空间换取时间的做法,随着计算机不断地更新换代,一般不需要担心内存空间不足的问题,逐渐地从更多考虑的是时间上的问题。同时为了节约空间的开销,提出位段
二、结构体实现位段
2.1 位段的概念
位段是结构体的一种变形,在功能、用法上与结构体基本一致,但是在于内存分配上不同,位段可以很好的节省空间,可存在位段跨平台的问题。同时与结构体相比有两个点不同。
- 成员上:
int
、unsigned int
或signed int
,但是在C99中是可以选择其他类型 - 格式上:位段成员名后面有一个冒号和一个数字
struct A
{
char _a:2;
char _b:5;
};
【说明】:这里数字代表的是该成员变量占用空间大小,而大小单位是比特
【问题】:位段A所占的内存大小是多大?
这个问题,需要利用下面的知识了
2.2 位段的内存分配
- 位段成员:
int
、unsigned int
、signed int
或者char
等类型(需要是整形,是要转换为二进制) - 位段开辟空间的大小一般是以四个字节或一个字节开辟的
- 位段涉及许多不确定的因素,位段是不跨平台的,注意可移植的程序,应该避免使用位段
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;
2.3 位段的跨平台问题
【不确定的因素大致包括】:
- 内存存放的方向是从左到右,还是从右到左
- 是低地址到高地址,还是高地址到低地址
- int类型是不确定是被当作有符号数还是无符号数
- 当一个结构体包括了两个位段,第二个位段比较大,无法容纳第一个位段剩下的空间,是舍弃还是利用剩下的空间,这是不确定的
- 位段中最大位的数目不能确定(16位机器最大16,32位机器最⼤32,写成27,在16位机器会出问题),可能会冲出最大的范围,出现问题
我们不妨以vs2013环境下测量下数据
vs2013下,位段是从左到右,从低地址到高地址,位段需要的空间不足,直接开辟一块新的空间,我们来结合图片理解下
【步骤】:
- 位段开辟八个bit位(这里是char类型的情况)
- 位段成员后面数字是占用多少bit位
- 根据变量数据,转化为二级制(一个二级制为一个比特位),根据位段对应的数据,将转为的二级制多个比特位放入
- 关于上不确定因素中(4),vs2013选择舍弃,那就开辟一块新的空间,重复(1,2,3)步骤
2.4 位段的应用
比如下图中网络协议中,在一个结构存在很多只需要几个bit位就能实现的效果,这里使用位段就能达到想要的效果,也能节省空间的浪费。同时网络传输的数据大小也会小一点,提高了网络的流畅和效率!
【位段使用注意事项】:
struct A
{
int _a:2;
int _b:5;
};
int main()
{
//错误的做法
struct A s={0};
scanf("%d",&s._a);
//正确的示范
int b=0;
scanf("%d",&b);
s._b=b;
return 0;
}
【说明】:
位段的几个成员共有同一个字节,而有些成员的起始位置并不是某个字节的起始位置。对此这些位置是没有地址(内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的)
【解决办法】:
可以将值放入一个变量中,再通过赋值给位段成员,这个赋值在以后的操作中,是很巧妙的用法的。
以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二C语言笔记,希望对你在学习C语言中有所帮助!