首页 > 系统相关 >内存对齐

内存对齐

时间:2023-04-17 14:58:13浏览次数:37  
标签:字节 int 内存 pragma 对齐 pack

1、什么是内存对齐

还是用一个例子带出这个问题,看下面的小程序,理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。

//32位系统
#include<stdio.h>
struct{
    int x;
    char y;
}s;

int main()
{
    printf("%d\n",sizeof(s);  // 输出8
    return 0;
}

现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。

2、为什么要进行内存对齐

1.尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度。(平台原因)

2.现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。

3.假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的连续四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址,也就是它占了地址1~3和地址4,但是0~3,4~7都是它的),最后留下的两块数据合并放入寄存器,这需要做很多工作。

现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。

3、内存对齐规则

1.每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。gcc中默认#pragma pack(4),可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。

2.有效对齐值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个min{max{#pragma pack(n)},max{struct}}。有效对齐值也叫对齐单位。

3.了解了上面的概念后,我们现在可以来看看内存对齐需要遵循的规则:

(1) 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。

(3) 结构体的总大小为有效对齐值的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

下面给出几个例子以便于理解:

//32位系统
#include<stdio.h>
struct
{
    int i;    
    char c1;  
    char c2;  
}x1;

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

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

int main()
{
    printf("%d\n",sizeof(x1));  // 输出8
    printf("%d\n",sizeof(x2));  // 输出12
    printf("%d\n",sizeof(x3));  // 输出8
    return 0;
}

以上测试都是在Linux环境下进行的,linux下默认#pragma pack(4),且结构体中最长的数据类型为4个字节大于等于#pragma pack(4),所以有效对齐单位为4字节,下面根据上面所说的规则以s2来分析其内存布局:

首先使用规则1,对成员变量进行对齐:

sizeof(c1) = 1 <= 4(有效对齐位),按照1字节对齐,占用第0单元;

sizeof(i) = 4 <= 4(有效对齐位),相对于结构体首地址的偏移要为4的倍数,占用第4,5,6,7单元;

sizeof(c2) = 1 <= 4(有效对齐位),相对于结构体首地址的偏移要为1的倍数,占用第8单元(注意不能放在别人的单元格中,也不能放在别人的填充区域);

然后使用规则2,9不是4的倍数,9往后12是4的倍数,因此结构体占12个字节,这样就对结构体整体进行了对齐。

根据上面的分析,不难得出上面例子三个结构体的内存布局如下:

4.#pragma pack(n)

不同平台上编译器的 pragma pack 默认值不同。而我们可以通过预编译命令#pragma pack(n), n= 1,2,4,8,16来改变对齐系数。 例如,对于上个例子的三个结构体,如果前面加上#pragma pack(1),那么此时有效对齐值为1字节,此时根据对齐规则,不难看出成员是连续存放的,三个结构体的大小都是6字节。

如果前面加上#pragma pack(2),有效对齐值为2字节,此时根据对齐规则,三个结构体的大小应为6,8,6。内存分布图如下:

经过上面的实例分析,大家应该对内存对齐有了全面的认识和了解,在以后的编码中定义结构体时需要考虑成员变量定义的先后顺序了。

 

参考文章:
(60条消息) C语言深度理解结构体(内存对齐、位段、偏移量、柔性数组)_字节偏移和位偏移_SPMAX的博客-CSDN博客

C/C++内存对齐详解 - 知乎 (zhihu.com)

标签:字节,int,内存,pragma,对齐,pack
From: https://www.cnblogs.com/Sandals-little/p/17325827.html

相关文章

  • innodb的体系结构(内存篇一)
    上图innodb存储引擎的架构引用官方手册,从上图来看关于架构的相关功能看起来很复杂,实际上也确实很复杂。为了方便理解我用黄红绿三个框稍微给归纳一下,分成三个部分。简单的概括一下,当然没有很全面。 黄框是关于内存方面的功能;红框是将数据从内存落实到硬盘的相关功能;绿框是......
  • innodb的体系结构(内存篇二)
    本篇文章介绍同在内存结构中的另一个缓冲池“changebuffer”和“logbuffer”“changebuffer”在innodb的1.0版本之前叫“insertbuffer”主要用于辅助索引(二级索引,非聚簇索引)的dml操作。什么是“聚簇索引”和“辅助索引”,聚簇索引就是选择主键建立的索引如果没有选择主键......
  • 关于vite项目内存溢出无法正常打包的问题
    问题在vite+ts+vue3项目中时pnpmrunbuild构建项目导致无法正常打包解决思路排查问题时发现禁用router插件后能正常打包,禁用掉则会导致溢出。进一步发现是因为.vue文件过多的原因。考虑到可能是nodejs默认的内存不够,增大内存试试。增加到24GB后可以成功打包。项目继续开......
  • css中文字与图片对齐
    css中文字与图片对齐第一种,使用vertical-align:middle.icon{ background-repeat:no-repeat; background-position:center;display:inline-block;width:16px;height:16px;vertical-align:middle;}.icon-down{background-image:url(./imgs/icon......
  • 内存相关
    redis中的info指标#redis中key-value使用的内存量used_memory:2709112used_memory_human:2.58M#从操作系统层面redis内存占用量used_memory_rss:5378048used_memory_rss_human:5.13M#内存使用峰值used_memory_peak:6786120used_memory_peak_human:6.47Mused_me......
  • C++中的虚函数表实现机制——对于虚表的内存布局讲解得非常好
    C++中的虚函数表实现机制摘自:https://blog.twofei.com/496/前言大家都应该知道C++的精髓是虚函数吧?虚函数带来的好处就是:可以定义一个基类的指针,其指向一个继承类,当通过基类的指针去调用函数时,可以在运行时决定该调用基类的函数还是继承类的函数.虚函数是实现多态(......
  • 内存对齐详解 (C++代码)
    内存对齐详解(C++代码)目录内存对齐详解(C++代码)Test1:结构体对齐Test2:时间开销对比我每天都有读一下面经的习惯,从里面抽一些我不太懂的内容使用dfs的思想去探索,感谢有了GPT4,探索之路变得有趣和高效许多。今天这个Topic如下,是腾讯日常实习面经中的内容编译内存相关:C+......
  • 一文弄懂Python中的内存管理
    1.引言Python是一种解释性语言,这意味着它在运行之前不需要编译。当Python程序运行时,它会动态地为所有变量和对象分配相应的内存。这意味着Python的内存管理是自动处理的,使得开发人员能够专注于编写代码,而不用担心相关内存分配和释放。本文就Python的内存管理进行详述,闲话少说,我们......
  • Delphi FDMemTable内存表用法及简单操作函数封装(转)
    在某些场景下当轻量级的应用需要在内存中缓存数量比较多且字段比较多的高频使用数据时。以前我都是采用Ini或直接使用sqlite数据库。JSON也试过基本无法或很难实现需要的功能,因为当涉及某一同类型对象多字段多列时不通过遍历基本无法直接取到或修改数据。这样就导致了效率的低下。......
  • c++核心编程—内存分区模型
    一、概述c++在执行时,将内存大致分为4个区域1、代码区:存放函数体的二进制代码,由操作系统进行管理2、全局区:存放全局变量和静态变量以及常量3、栈区:由编译器自动分配释放,存放函数的参数,局部变量等4、堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收内存四区意义:不同区......