【C语言进阶(七)】自定义类型--结构体,位段,联合

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C语言学习分享⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习更多C语言知识

🔝🔝



自定义类型

  • [1. 前言](#1. 前言 "#1__14")
  • [2. 结构体内存大小问题](#2. 结构体内存大小问题 "#2__28")
  • [3. 结构体内存对齐规则](#3. 结构体内存对齐规则 "#3__71")
    • [3.1 偏移量的概念](#3.1 偏移量的概念 "#31__77")
    • [3.2 内存对齐规则](#3.2 内存对齐规则 "#32__82")
    • [3.3 内存对齐规则实例分析](#3.3 内存对齐规则实例分析 "#33__111")
    • [3.4 回头验证最初的数据](#3.4 回头验证最初的数据 "#34__154")
  • [4. 存在内存对齐规则的原因](#4. 存在内存对齐规则的原因 "#4___187")
  • [5. 位段](#5. 位段 "#5__215")
    • [5.1 位段的内存分配规则](#5.1 位段的内存分配规则 "#51__248")
  • [6. 联合(共用体)](#6. 联合(共用体) "#6__272")
    • [6.1 联合大小计算](#6.1 联合大小计算 "#61__301")
  • [7. 总结以及拓展](#7. 总结以及拓展 "#7__335")

1. 前言

文章目标:

本篇文章着重给大家讲解:
结构体内存对齐的知识
并且介绍位段,联合的内容
最后对这一板块做出拓展

结构体,位段和联合
这哥几个的区别和关联到底是什么?


2. 结构体内存大小问题

首先看下面这段代码:

c 复制代码
struct S1
{
	 char c1;
	 int i;
	 char c2;
};

struct S2
{
	 char c1;
	 char c2;
	 int i;
};

这两个结构体中存放的都是
两个char类型和一个int类型数据
所以暂时推断出它们所占内存空间相同

那它们占了多大的空间呢?
char+char+int ,1+1+4=6
我们推断出它至少占6个字节

来验证一下:

c 复制代码
printf("%d\n",sizeof(struct S1));
printf("%d\n",sizeof(struct S2));

结果很出乎意料,它们不仅内存不相同
并且和我们推断出的6也没有太大关系!


3. 结构体内存对齐规则

出现以上原因是因为:
结构体有特殊的内存对齐规则:


3.1 偏移量的概念

在这儿之前先介绍偏移量的概念:


3.2 内存对齐规则

对齐规则:

  1. 第一个成员在偏移量为0的地址处
  2. 其他成员要对齐到对齐数的整数倍处
  3. 结构体总大小为成员中最大对齐数的整数倍

对齐数概念:

对齐数 = min(编译器默认的对齐数 , 该成员大小)

  • VS中默认对齐数为8
  • linux中对齐数就是成员自身大小

比如在VS编译环境下:

c 复制代码
struct S3
{
	double d;
	char c;
	int i;
};
  • d的大小是8,默认对齐数是8,对齐数就是8
  • c的大小是1,默认对齐数是8,对齐数就是1
  • i的大小是4,默认对齐数是8,对齐数就是4

3.3 内存对齐规则实例分析

拿上面的例子来分析

c 复制代码
struct S3
{
  double d;
  char c;
  int i;
};

已知:d的对齐数为8
c的对齐数位1
i的对齐数为4
结构体最大对齐数为8

可得:

对于图片的解释:

  1. d是第一个成员,所以它直接放在
    偏移量为0的位置
  2. c是第二个成员,它的对齐数是1
    任何一个数都是1的倍数,所以
    c紧接着放在d内存的后面
  3. i 是第三个成员,它的对齐数是4
    而c的后面是9, 9不是4的倍数
    10也不是4的倍数,直到12才是
    4的倍数,所以i从12开始放
  4. 最后一个成员放完后的位置是15
    而结构体最大对齐数是8
    15不是8的倍数,16才是
    所以最终在16停止

3.4 回头验证最初的数据

最开始的两个结构体:

c 复制代码
struct S1
{
	char c1;
	int i;
	char c2;
};

struct S2
{
	char c1;
	char c2;
	int i;
};

1. 对于S1而言是这样的情况:

这也就解释清楚为什么会打印12出来的

对于S2而言是这样的情况:


占8个字节也解释清楚了!


4. 存在内存对齐规则的原因

  1. 原因1:平台原因(移植原因):

不是所有的硬件平台都能访问
任意地址上的任意数据的
某些硬件平台只能在某些地址处取
某些特定类型的数据,否则抛出硬件异常

  1. 原因2:性能原因

如果不存在内存对齐的话
平台4个字节4个字节的访问
int类型的数据时有可能需要
读取两次才能取到一个数据

总的来说:内存对齐是拿空间换取时间

  1. 节省空间的技巧:

发现同样的成员类型和数量
成员放的位置不同,结构体大小
也存在的很大的区别

在写结构体时尽量将占用空间
小的数据集中在一起能节省空间


5. 位段

位段和结构的声明非常相似

但又存在下面这两个不同:

  1. 位段的成员必须是整型家族(int/char)
  2. 位段的成员名后边有一个冒号和一个数字

比如:定义一个位段

c 复制代码
struct A
{
  int _a:2;
  int _b:5;
  int _c:10;
  int _d:30;
};

这个位段的大小是多少呢?
肯定不会是4×4=16个字节这么简单

c 复制代码
printf("%d\n",sizeof(struct A));

结果是8,我们来简单分析一下:


5.1 位段的内存分配规则

位段是具有不确定性的,不能跨平台
它在每一个编译器下可能有所不同

我只介绍在VS编译器下的具体规则:

先初始化一下结构体:

c 复制代码
s.a = 3;
s.b = 12;
s.c = 3;
s.d = 4;

冒号后面的数字代表
变量所占的二进制位(比特位)

画图解释:


6. 联合(共用体)

联合也是一种自定义类型
它其中的变量共用同一份空间!

它的不同:

  1. 成员共用同一份空间
  2. 不用struct定义,而是用union定义
  3. 联合的大小至少是最大成员的大小

比如:

c 复制代码
union Un
{
  int i;
  char c;
};
union Un un;//定义联合变量
printf("%d\n", &(un.i));//它们的地址相同
printf("%d\n", &(un.c));//共用同一份空间

6.1 联合大小计算

联合共用体和结构体一样

有内存对齐原则,不过联合的比较简单:

  • 联合的大小至少是最大成员的大小
  • 最大成员大小不是最大对齐数的整数倍时
    就要对齐到最大对齐数的整数倍

比如:下面这两个联合

c 复制代码
union Un1
{
   char c[5];
   int i;
};

union Un2
{
   short c[7];
   int i;
};

printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));

它们的大小分别是:8和16


7. 总结以及拓展

结构体的内存对齐是面试的常考点!
掌握它不仅可以更深层次了解C语言
还可以在面试的时候给面试官一个震撼

拓展:修改默认对齐数

C语言提供了#pragma指令

来帮助我们解决这个问题:

假设我们想要将默认对齐数改为1:

c 复制代码
#pragma pack(1)//设置默认对齐数为1

struct S1
{
  char c1;
  int i;
  char c2;
};

假设我们又想修改回来:

c 复制代码
#pragma pack(1)//设置默认对齐数为1
struct S1
{
  char c1;
  int i;
  char c2;
};

#pragma pack()//取消设置的默认对齐数,还原为默认

struct S2
{
  char c1;
  int i;
  char c2;
};

上述代码中,结构体S1
使用的是默认对齐数为1
而S2使用的默认对齐数是8


拓展:利用联合求大小端

详细可以参考以下这篇文章:
利用联合体判断机器大小端


🔎 下期预告:动态内存管理 🔍

相关推荐
2035去旅行3 小时前
bootcmd 详细讲解
linux·嵌入式·arm·soc
陈希1233 小时前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡4 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
且从容.7 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道8 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.11 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落12 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨12 小时前
WSL2的安装与使用
linux·wsl
微小的鱼12 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng13 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器