结构体 · 内存对齐

news/2024/11/24 14:14:52/

欢迎来到 Claffic 的博客 💞💞💞

前言:

在初识C语言中简单介绍了结构体,结构体可以理解为不同类型数据的集合体,但是你想过结构体的大小是如何计算的吗?看完这篇博客,你就能给自己答案了。

注:此博客包含进阶知识,建议学完C语言初阶知识再进行学习哦 ~  


1.请看题

#include<stdio.h>
struct Test
{int i;char c;double d;
};
int main()
{printf("%d\n", sizeof(struct Test));return 0;
}

问:程序输出多少? 

提示:编译环境VS2022,默认对齐数为8字节(什么意思? 留个悬念)

题目的意思就是要求我们计算 Test 这个结构体的大小

我们初步猜测:4(int 的大小)+ 1(char 的大小)+ 8(double 的大小)== 13 ;

是不是呢?

是    就没有这篇博客啦

接下来让我们看看结构体的对齐是怎么规定的:

2.结构体的对齐规则

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

规则读起来吃力,更不要说理解了

没关系,跟我捋一遍解题的步骤

跟我做完后再回来看上面的对齐规则,相信你会恍然大悟哒!

2.1画图准备

可以像我这样拷一份代码列一个表格补上默认对齐数

我用的是 windows11 自带的默认画图软件。

2.2对齐

按照数据上往下的顺序(i --> c -->  d)开始对齐,

先是第一个 int 类型的 i ,没啥顾虑的,直接从 偏移量 为 0的地方开始填充 ,填充 4 个字节(一个 int 类型大小)

 

这里对应 1. 第一个成员 在与结构体变量 偏移量为0 的地址处;

接下来是 char 类型的 c ,这里要多考虑了:

首先是 c 的大小 与 默认对齐数 (揭开悬念)的比较,取两者中的较小值1 ,作为改成员的对齐数;

接着是对齐,我们看到下一个空间的偏移量是 4 ,是 1 的整数倍 没错,可以放心填充。

这一步对应 2. 其他成员变量 要对齐到 对齐数 的 整数倍 的地址处;
                       对齐数 = 编译器默认的一个对齐数 与 该成员大小的 较小值

然后是 double 类型的 d ,同上:

8 与 8 比较,就取 8,作为该成员的对齐数

看下一个空间的偏移量是 5 ,不是 8 的整数倍,接着向下找,直到 8 ,开始填充:

最后就是结构体的总大小了:

很清楚,三个成员的最大对齐数是 8 ,那么总大小是 8 的整数倍

目前填充到了 15 ,无奈,15 不是 8 的整数倍,只能继续向下找 ,嗯 ,那个值是 16。

这里对应 3. 结构体 总大小 最大对齐数(每个成员变量都有一个对齐数)的 整数倍

所以这道题的正确答案是 16 

3.结构体嵌套

就着刚才解的题,再看下面这道:

#include<stdio.h>
struct Test
{int i;char c;double d;
};struct Old
{int a;struct Test test;char b;
};int main()
{printf("%d\n", sizeof(struct Old));return 0;
}

其实就是刚才的结构体 Test 被嵌套

这里就只提及结构体部分,其余的解法同上题

结构体 Test 中,最大的对齐数是 8,所以从 8 的整数倍开始填充,它的大小就是 16,填充 16 个字节。 

答案是 32 ,大家可以自行解决。

读到这里,建议再回首去看 4 条结构体的对齐规则,相信你会有明白的感觉 ~

4.内存对齐的原因:

不难发现,按照内存对齐,的却有内存被浪费了,但为什么还要按照这种规则呢?

根据参考资料,原因有两个方面。

1. 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;

    某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐;
    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需      要一次访问。

简单解释,一次内存访问是有固定大小的,大小 4 或 8,以一次访问 4 字节为例:

没有内存对齐:

就单看 int 类型的数据

访问两次才能拼凑出一个 int;

有内存对齐:

访问一次就可把 int 读取,且每次访问不交叉,干净利索

总的来说,这是一种 拿空间换时间 的做法,目前还是最优解。 

这样说能解答一些疑惑吧,但并不是标准的说法,不可钻牛角尖哦 ~


总结:

此博客详细讲解了结构体的内存对齐规则,内容比较干,建议多咀嚼,消化理解

如果你觉得这篇文章还不错并且对你有帮助,不妨支持一波哦  💗💗💗

关注我  不迷路!!!


http://www.ppmy.cn/news/11360.html

相关文章

测试开发基础 mvn test | 利用 Maven Surefire Plugin 做测试用例基础执行管理

一、需求在测试工作场景中&#xff0c;经常会遇到下面的问题&#xff1a;1、执行自动化测试用例的时候&#xff0c;只想指定某个测试类&#xff0c;或者某个方法&#xff0c;又或者某一类用例等&#xff0c;怎么办&#xff1f;2、想要和 Jenkins 一起进行持续集成&#xff0c;可…

java 数组看这一篇文章就够了

第一章 数组概述 数组介绍1 2 3 41. 数组是多个相同类型数据的组合 2. 数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型 3. 数组属引用类型,数组中的每个元素相当于该对象的成员变量 4. 数组中的数据是有序的,可以通过序号(索引或者下标)获取,索引从0开始数组…

米筐量化终端是什么?

米筐量化终端大家应该也能想象到是应用的终端&#xff0c;是系统执行的终端环节&#xff0c;如果是用在量化方面&#xff0c;那它就是策略定制的终端&#xff0c;是方便投资者输入量化策略执行出来发最终优质目的&#xff0c;精确到细分股票的账户成交量&#xff0c;股价以及融…

刚当上leader,我让组员去开会,他非说有更重要的会

☆ 职场上经常有那么一种情况就是组长喊组员开会&#xff0c;开周会&#xff0c;开晨会&#xff0c;开各种会&#xff0c;而更有一种常见的情况呢就是组长缺失威严&#xff0c;喊组员开会&#xff0c;组员不听话&#xff0c;说有更重要的会议&#xff0c;不想参加。 ☆ 本文将以…

Kettle的安装以及简单使用

Kettle是一款开源免费的ETL工具&#xff0c;ETL全称 Extract - Transform - Load 意味着数据抽取&#xff0c;转换&#xff0c;装载的过程。 ETL是将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程&#xff0c;目的是将企业中的分散、零乱、标准不统一的数据整合到…

多线程案例

1.阻塞队列1.1阻塞队列的工作原理阻塞队列本质上还是一个队列&#xff0c;但是在队列的基础上加入了阻塞功能&#xff0c;并且线程安全。那么它的阻塞功能体现在两方面1.当队列为空时&#xff0c;进行出队列操作&#xff0c;就进入阻塞状态2.当队列满了时&#xff0c;进行入队列…

Flink多流转换(Flink Stream Unoin、Flink Stream Connect、Flink Stream Window Join)

文章目录多流转换1、分流操作1.1、在flink 1.13版本中已弃用.split()进行分流1.2、使用&#xff08;process function&#xff09;的侧输出流&#xff08;side output&#xff09;进行分流2、基本合流操作2.1、联合&#xff08;Flink Stream Union&#xff09;2.2、连接&#x…

1.5万字总结 Redis 常见面试题知识点

以下内容来源于于我开源的 JavaGuide (Java学习&&面试指南,Github 130k star,370人共同参与爱完善), 万字总结,质量有保障! Redis 基础 什么是 Redis? Redis 是一个基于 C 语言开发的开源数据库(BSD 许可),与传统数据库不同的是 Redis 的数据是存在内存中的…