当前位置: 首页 > article >正文

结构体内存对齐

目录

  • 一、什么是结构体内存对齐
  • 二、为什么要结构体内存对齐
  • 三、对齐系数
  • 四、结构体对齐规则
    • 1、例一:文章开头的例子
    • 2、例二:稍微复杂的情况
    • 3、结合 union 和 struct
    • 4、结构体嵌套


一、什么是结构体内存对齐

进入讲解前,先看一段 C 代码:

struct Node1 {
    int a;
    char b;
    char c;
} node1[1024];

struct Node2 {
    char b;
    int a;
    char c;
} node2[1024];

思考一下 node1node2 的大小分别为多少?

printf("The size of node1 is: %d\r\n", sizeof(node1));
printf("The size of node2 is: %d\r\n", sizeof(node2));

结果如下:

The size of node1 is: 8192
The size of node2 is: 12288

我是在 Windows 下 MinGW32 的 GCC 测试的

一样的成员属性,但 node1 只有 8K,而 node2 的大小却有 12K。

由此可见,结构体对齐,实质上就是内存对齐。

二、为什么要结构体内存对齐

为什么要结构体对齐,原因就是内存要对齐,原因是芯片内存的制造限制,是制造成本约束,是内存读取效率要求。

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,

  • 为了访问未对齐的内存,处理器需要作两次内存访问;
  • 对齐的内存访问仅需要⼀次访问。

假设一个处理器总是从内存中取 8 个字节,则地址必须是 8 的倍数。如果我们能保证将所有的 double 类型的数据的地址都对齐成 8 的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个 8 字节内存块中。

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

三、对齐系数

对齐系数是指编译器在内存布局中对结构体成员进行对齐的要求。由于硬件访问内存的方式有一定的要求,结构体的对齐方式会影响内存的使用效率和访问速度。

在大多数编译器中,结构体对齐系数是通过编译器的编译选项或者特定的指令来指定的。对齐系数通常以字节为单位,表示结构体成员的起始位置必须是该字节数的整数倍。

注意,不同的平台,模数是不一样的。

struct Node3 {
	int a;
	double b;
} node3;

printf("The size of node3 is: %d\r\n", sizeof(node3));
printf("node3.a address: %p\r\n", &node3.a);
printf("node3.b address: %p\r\n", &node3.b);

结果如下:

The size of node3 is: 16
node3.a address: 003CF1B8
node3.b address: 003CF1C0

可知,对齐系数就是 8.

当然,对齐模数是可以改变的,可以用预编译命令 #pragma pack(n),n=1,2,4,8,16(必须是 2 的幂次方)来改变这一系数,其中的 n 就是你要指定的“对齐系数”。

现在将上面的结构体改变一下:

#pragma pack(4)
struct Node3 {
	int a;
	double b;
} node3;
#pragma pack()

将它的对齐模数改为了 4,结果如下:

The size of node3 is: 12
node3.a address: 0042F1B8
node3.b address: 0042F1BC

四、结构体对齐规则

  1. 结构体的内存大小,并非其内部元素大小之和;
  2. 结构体变量的起始地址,可以被最大元素基本类型大小或者模数整除;
  3. 结构体的内存对齐,按照其内部最大元素基本类型或者模数大小对齐;
  4. 模数在不同平台值不一样,也可通过 #pragma pack(n) 方式去改变,其中 n 一定是 2 的幂次方,如 1,2,4,8,16 等;
  5. 如果空间地址允许,结构体内部元素会拼凑一起放在同一个对齐空间;
  6. 结构体内有结构体变量元素,其结构体并非展开后再对齐;
  7. unionbitfield 变量也遵循结构体内存对齐原则。

下面结合例子来看一下:

1、例一:文章开头的例子

对于本文一开始提到的例子,它们的内存对齐方式如下(这里就不以数组举例了):

struct Node1 {
	int a;
	char b;
	char c;
} node1;

struct Node2 {
	char b;
	int a;
	char c;
} node2;

解释如下:

node1 中的元素 a 是 int 类型,按 4 个字节对齐,其地址位是 4 的整数倍;而 b 和 c 就按 1 字节对齐,就跟在 a 后面就行了。

node2 中 b 是按 1 个字节对齐,放在最前面;而 a 是按 4 个字节对齐,其地址位必须是 4 的整数倍,所以,只能找到个 +4 的位置;紧接着 c 就按 1 字节对齐,跟其后面。

2、例二:稍微复杂的情况

struct Node4 {
	char  a;
	short b;
	char  c;
	int   d;
	char  e;
} node4;

结果如下:

The size of node4 is: 16
node4.a address: 00E1F1C4
node4.b address: 00E1F1C6
node4.c address: 00E1F1C8
node4.d address: 00E1F1CC
node4.e address: 00E1F1D0

其内存分布如下:

3、结合 union 和 struct

struct Node5 {
	int a;        
	union {
		char ua[9];      
		int ub;
	}u;      
	double b;  
	int c;
} node5;

如果 union 里的元素类型不一样,那就以最大长度的那个类型对齐了。

4、结构体嵌套

typedef struct {
	int a;
	char b;
} node1;

struct Node11 {
	node1 nd1;
	char c;
} node11;

typedef struct {
	short a;
	char b;
} node2;

struct Node22 {
	node2 nd2;
	char c;
} node22;
The size of node11 is: 12
node11.nd1 address: 0028F1D4
node11.c address: 0028F1DC

The size of node22 is: 6
node22.nd2 address: 0028F1E0
node22.c address: 0028F1E4

可以看见,结构体内的结构体,结构体内的元素并不会和结构体外的元素合并占一个对齐单元。


http://www.kler.cn/a/306495.html

相关文章:

  • ubuntu连接orangepi-zero-2w桌面的几种方法
  • linux c/c++最高效的计时方法
  • qt QKeySequence详解
  • [运维][Nginx]Nginx学习(1/5)--Nginx基础
  • 【练习案例】30个 CSS Javascript 加载器动画效果
  • MyBatis CRUD快速入门
  • 【算法】动态规划—最长公共子序列
  • HTML+CSS - 网页布局之多列布局定位
  • 网络安全应急响应概述
  • 用STM32做一个USB-TTL工具吧
  • JavaScript Promise 异步编程的一些代码分享
  • 远程桌面内网穿透是什么?有什么作用?
  • openssl下载和创建证书
  • 如何在 Visual Studio Code 中反编译具有正确行号的 Java 类?
  • C++:opencv多边形逼近二值图轮廓--cv::approxPolyDP
  • Java集合进阶--双列集合
  • R与机器学习系列|15.可解释的机器学习算法(Interpretable Machine Learning)(下)
  • HarmonyOS开发5.0【rcp网络请求】
  • ChatGPT+2:修订初始AI安全性和超级智能假设
  • L298N电机驱动方案简介
  • JAVA:Nginx(轻量级的Web服务器、反向代理服务器)--(1)
  • JAVA学习-练习试用Java实现“串联所有单词的子串”
  • 物联网——USART协议
  • 揭开OpenAI草莓模型神秘面纱——重塑大语言模型的逻辑能力
  • np.argpartition 是 NumPy 库中的一个非常有用的函数,具体用法如下:
  • 力扣周赛:第415场周赛