结构体 · 内存对齐

简介: 结构体 · 内存对齐

前言:

在初识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画图准备


625c6c4a93445a24ee99c3fdf09e3db2_1199b7e2926645648be81edfd824516c.png

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

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


2.2对齐


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

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

f54b3b4df7c4ae27950e789b1df315c6_11ce87d1ffcc4e06892a34749ba5323d.png

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

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

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

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

4dfa6059b07236bc2a4606390ab27150_2544d925cd044ad1b56acfae079ea4ee.png

这一步对应 2. 其他成员变量 要对齐到 对齐数 的 整数倍 的地址处;

                    对齐数 = 编译器默认的一个对齐数 与 该成员大小的 较小值;

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

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

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

9c55b1b2d4fdf06f53b080d5c46c44fb_fbab846e9f09432495180fe0ef0a7e22.png

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

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

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

8e8b0a9813d3bb490a74098c776db1e6_fa98a94dd5784852a2f5f301af9ecc65.png

这里对应 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 字节为例:

没有内存对齐:

a1061690a2379354c2448bbf3f0d7493_11957e4a559b4d9fa3ed764dafc8c2f2.png

就单看 int 类型的数据

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

有内存对齐:

b72b21ddc8c63dca303d694952357e65_366e86609d544156adfbccb539cddf10.png

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

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

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


总结:

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

目录
相关文章
|
编译器 C语言 C++
C/C++内存对齐规则(结构体、联合体、类)
C/C++内存对齐规则(结构体、联合体、类)
|
安全 C++
【自定义类型:结构体,枚举,联合】内存对齐的原理和原因
【自定义类型:结构体,枚举,联合】内存对齐的原理和原因
79 0
|
编译器 Linux C语言
【C语言】自定义类型:结构体(内存对齐),枚举,联合
【C语言】自定义类型:结构体(内存对齐),枚举,联合
|
6月前
|
存储 编译器 Linux
匿名结构体类型、结构体的自引用、结构体的内存对齐以及结构体传参
匿名结构体类型、结构体的自引用、结构体的内存对齐以及结构体传参
|
11天前
|
存储 Java 程序员
结构体和类的内存管理方式在不同编程语言中的表现有何异同?
不同编程语言中结构体和类的内存管理方式既有相似之处,又有各自的特点。了解这些异同点有助于开发者在不同的编程语言中更有效地使用结构体和类来进行编程,合理地管理内存,提高程序的性能和可靠性。
21 3
|
13天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
14天前
|
存储 缓存 算法
结构体和类在内存管理方面有哪些具体差异?
【10月更文挑战第30天】结构体和类在内存管理方面的差异决定了它们在不同的应用场景下各有优劣。在实际编程中,需要根据具体的需求和性能要求来合理选择使用结构体还是类。
|
3月前
|
存储 Go
Go 内存分配:结构体中的优化技巧
Go 内存分配:结构体中的优化技巧
|
5月前
|
编译器 测试技术 C语言
【C语言】:自定义类型:结构体的使用及其内存对齐
【C语言】:自定义类型:结构体的使用及其内存对齐
69 7
|
5月前
|
存储 编译器 C语言
C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)一
C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)一
59 2