Linux性能学习(2.3):内存_为什么分配的内存比申请的内存大16个字节

news2024/11/17 1:35:38

文章目录

  • 1 验证申请不同内存,系统分配机制
    • 1.1 代码
    • 1.2 测试
    • 1.3 结论
  • 2 为什么会多分配内存
  • 3 为什么会有4字节不可使用

参考资料:

  1. https://www.gnu.org/software/libc/

在上一篇文章中,探讨了Linux系统对进程以及线程的内存分配问题,然后采用申请1KB内存的方式进行验证,然后发现将第二次申请的内存地址减去第一次申请内存的地址,长度为1040,比我们申请的1024多了16个字节,从而提出一个问题,“在64位系统中,为什么系统分配的内存比实际申请的内存大16个字节?”。

但是上一篇的文章主要是探讨内存分配的,所以对这个问题没有过多追究,提出这个问题也不严谨,因为只是申请了1024个字节,系统分配了1040,多了16个字节,那么如果申请1个字节、2个字节、3个字节等等,系统又是如何分配的,是否还是多分配16字节?64位系统和32位系统是否都是多分配16字节?

所以下面我们先验证下,如果我们申请不同大小的内存,系统是否还是会多申请16字节的内存?

1 验证申请不同内存,系统分配机制

1.1 代码

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int i = 0;
	char* s8New = NULL;
	char* s8Old = malloc(0);
	
	if (NULL == s8Old)
	{
		printf("malloc err\n");
	}
	else
	{
		printf("malloc success, malloc size:%d, usable_size:%d, addr:%p, ", 0, malloc_usable_size(s8Old), s8Old);
	}
		
	for (i = 1; i < 20480; i++)
	{	
		s8New = (char*)malloc(i);
		if (NULL == s8New)
		{
			printf("malloc err\n");
		}
		
		if ((NULL != s8New) && (NULL !=  s8Old))
		{
			printf("addr size:%d\n", s8New - s8Old);
			s8Old = s8New;
			printf("malloc success, malloc size:%d, usable_size:%d, addr:%p, ", i, malloc_usable_size(s8Old), s8Old);
		}
		else
		{
			printf("malloc success, malloc size:%d, usable_size:%d, addr:%p, ", i, malloc_usable_size(s8New), s8New);
		}
	}
	
	return 0;
}

上述代码的功能是从1个字节开始,逐步增加,直到申请2MB的内存,查看系统分配情况,会有三个主要参数的打印:“malloc size”表示我们申请的内存,“usable_size”是使用malloc_usable_size来获取系统实际分配的大小,“addr size”为下一个申请的内存地址减去当前申请内存的地址,即为当前申请内存的大小。

1.2 测试

在Ubuntu 64位系统下测试,结果如下:
在这里插入图片描述

在32位系统下测试,结果如下:
在这里插入图片描述

上面的数据,只是部分数据,经过综合,得出如下数据:
64位系统
在这里插入图片描述

32位系统
在这里插入图片描述

1.3 结论

通过上面的表格,我们可以得出如下结论:

  • A.在我们申请内存的时候,系统可能会申请多的内存给到我们,具体规则是:
    32位系统下:N8+4,N为1~无穷大,(N8+4)的值为最接近与我们申请的内存的值;
    64位系统下:N16+8,N为1~无穷大,(N16+8)的值为最接近与我们申请的内存的值。
  • B.申请的内存的地址相减会比实际申请的内存usable_size还要大,在32位系统下大4个字节,在64位系统下大8个字节。即实际分配的内存地址范围大小为:
    32位系统下:N8+8;
    64位系统下:N
    16+16。

2 为什么会多分配内存

针对上面的结论A,我们进行分析查找原因。

在上面我们使用malloc来申请内存,那么这些问题就跟malloc有关了,我们查看下malloc相关的代码,看看有啥收获。

源码可通过上面参考链接进行下载,在malloc.c/_int_malloc函数中,我们看到了checked_request2size函数,它的作用是将我们需要申请的字节大小转换为内部的大小,通过字节对齐等方式进行转换,来获取最小MINSIZE(最小可分配大小)的大小。
在这里插入图片描述

在这里,我们得到了一个信息MINSIZE,我们进到checked_request2size函数里面进行查看,
在这里插入图片描述

在checked_request2size中的request2size中我们可以看到,如果 (req) + SIZE_SZ + MALLOC_ALIGN_MASK的大小小于MINSIZE,那么就返回MINSIZE,如果大于,则进行对齐操作,再返回。
现在我们获取到结果宏参数:SIZE_SZ、MALLOC_ALIGN_MASK、MINSIZE

SIZE_SZ的大小通过代码追踪就是unsigned int的长度,在32位系统中就是4个字节;
MALLOC_ALIGN_MASK相关的定义如下:

/* The corresponding bit mask value.  */
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
/* MALLOC_ALIGNMENT is the minimum alignment for malloc'ed chunks.  It
   must be a power of two at least 2 * SIZE_SZ, even on machines for
   which smaller alignments would suffice. It may be defined as larger
   than this though. Note however that code and data structures are
   optimized for the case of 8-byte alignment.  */
#define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
			  ? __alignof__ (long double) : 2 * SIZE_SZ)
			  

可以得出,MALLOC_ALIGNMENT在32位系统中的长度为2*SIZE_SZ,即长度为8,那么MALLOC_ALIGN_MASK的长度就为7了。
MINSIZE的相关定义如下:

#define MINSIZE  \
  (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
#define MIN_CHUNK_SIZE        (offsetof(struct malloc_chunk, fd_nextsize))

MIN_CHUNK_SIZE 展开如下:

#define MIN_CHUNK_SIZE        (offsetof(struct malloc_chunk, fd_nextsize))
# define offsetof(type,ident) ((size_t)&(((type*)0)->ident))
===》等价于
#define MIN_CHUNK_SIZE (size_t) & ((struct malloc_chunk*)NULL) -> fd_nextsize)

struct malloc_chunk的定义如下:

struct malloc_chunk {

  INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

所以上面代码的大致意思是,取malloc_chunk中fd_nextsize的地址,这样得到的地址就是这个成员在结构体中的首地址。所以这个结构体中,必须需要的是前4个,后面两个仅用于large blocks,所以在32位的系统中,这个结构体的大小为44=16字节,在64位上为84=32位,或者4+4+8+8=24位。
由此,可以得到:

#define MINSIZE =(((16+7) & ~7))=16。

至此,我们可以得出如下信息,在32位系统中,一些参数值如下:

SIZE_SZ 4
MALLOC_ALIGNMENT 2*4=8
MALLOC_ALIGN_MASK 8-1=7
MIN_CHUNK_SIZE 16
MINSIZE 16

那么request2size宏定义可以换算如下:

#define request2size(req)       (((req) + 4 + 7 < 16)  ? 16 :                                                       ((req) + 4 + 7) & ~7)

将上面1.2章节中32位申请的内存对照表和上面的request2size中进行对照,结论一致。

同理,在64位系统中,一些参数值如下:

SIZE_SZ 8
MALLOC_ALIGNMENT 2*8=16
MALLOC_ALIGN_MASK 16-1=15
MIN_CHUNK_SIZE 32
MINSIZE 32

至此,我们可以得出结论,当我们申请内存时候,系统会根据自身的机制分配大于我们申请的内存的大小,具体分配大小参考request2size进行确认。

3 为什么会有4字节不可使用

针对问题B进行分析,在上面,我们看到在32位系统中,addr size比实际可使用的内存大小usable_size大4个字节,为什么会有这4个字节的浪费,或者说不能使用?

在malloc.c中有如下解释:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面的解释大致如下:
我们申请的chunk主要由Size of previous chunk + Size of chunk+user data等几部分构成的,chunk指针指向chunk开始的地方,mem指针是提供给用户的指针地址,从这个这个地址可以使用,进行读写等操作。
在这里插入图片描述

空闲的chunk是存储是双向循环链表中的,结构体是由 Size of previous chunk + Size of chunk+fd+bk等4部分组成的,参考下图:
在这里插入图片描述

上面的A/M/P三个参数:

  • P:如果P为0,表示前一个chunk为空闲,则prev_size中的值才有效,表示上一个空闲chunk的大小;如果P为1,则前一个chunk正在使用,prev_size无效。
  • M:如果为1,则是通过mmap方式分配的内存;如果为0,则是通过heap方式分配的内存。
  • A:为0表示主分区分配的内存;为1表示非主分配区分配的内存。

通过上图可以看到,一个chunk有head和foot,都是表示当前chunk大小,但是foot已经在next chunk了,即next chunk的Size of previous chunk ,同时为了提高chunk的有效载荷数据,Size of previous chunk 这个数据段也会用来存储数据,所以一个chunk可以由head+mem两部组成。而head的长度为SIZE_SZ的长度,即4个字节。

所以可以理解为,在32位系统下,linux申请的内存减去4个字节的长度,剩下的长度均为有效数据长度,即我们可以使用的长度。

同理,在64位系统下,linux申请的内存减去8个字节的chunk size字段,剩下的便是可以使用的数据长度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/389505.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

流程图简介

一、流程与流程图1. 什么是流程具体来说&#xff0c;流程是一项活动或一系列连续有规律的事项或行为进行的程序。流程有6个要素&#xff0c;分别是&#xff1a;资源、过程、结构、结果、对象和价值。一个流程会把这些基本要素串联起来&#xff0c;例如流程中资源的输入、流程中…

gprof2dot perf

什么是gprof2dot 这是一个用于将许多探查器的输出转换为点图Python脚本。 使用需要安装的依赖 Python: known to work with version 2.7 and 3.3; it will most likely not work with earlier releases.Graphviz: tested with version 2.26.3, but should work fine with ot…

【C++】类和对象补充知识点

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、再谈构造函数1.1 构造函数体赋…

逻辑电路代数运算(上)

逻辑代数L是一个封闭的代数系统&#xff0c;由一个逻辑变量集K&#xff0c;常量0和1&#xff0c;以及与或非三种基本运算构成。 参与逻辑运算的变量叫逻辑变量&#xff0c;用字母A&#xff0c;B……表示。每个变量的取值非0 即1。 0、1不表示数的大小&#xff0c;而是代表两种不…

三天吃透Java基础八股文

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

Asp.net core api swagger显示中文注释

在你的 Web API 项目中使用 Swagger 的.NET Core 封装 Swashbuckle 可以帮助你创建良好的文档和帮助页面&#xff0c;Swagger (OpenAPI) 是一个与语言无关的规范&#xff0c;用于描述 REST API。 它使计算机和用户无需直接访问源代码即可了解 REST API 的功能1、OpenAPI 与 Swa…

IP定位离线库有什么作用?

IP离线是什么意思&#xff1f;我们以丢失手机为例来寻找它&#xff0c;现在手机都有IP定位功能&#xff0c;只要手机开通了IP定位&#xff0c;就能找到手机。iPhone定位显示离线一般是iPhone手机关机了或者iPhone手机中“查找我的iPhone”功能关闭了。如果手机在手中的话可以打…

【Spark】Spark的DataFrame向Impala写入数据异常及源码解析

背景 事情是这样的&#xff0c;当前业务有一个场景: 从业务库的Mysql抽取数据到Hive 由于运行环境的网络限制&#xff0c;当前选择的方案&#xff1a; 使用spark抽取业务库的数据表&#xff0c;然后利用impala jdbc数据灌输到hive。&#xff08;没有spark on hive 的条件&…

cmd命令教程

小提示&#xff1a; 在本文中&#xff0c;我将向您展示可以在 Windows 命令行上使用的 40 个命令 温馨提示&#xff1a;在本教程中学习使用适用于 Windows 10 和 CMD 网络命令的最常见基本 CMD 命令及其语法和示例 文章目录为什么命令提示符有用一、cmd是什么&#xff1f;如何在…

AcWing 4868. 数字替换(DFS + 剪枝优化)

AcWing 4868. 数字替换&#xff08;DFS 剪枝优化&#xff09;一、问题二、思路三、代码一、问题 二、思路 题目中要求变换次数最小&#xff0c;其实第一印象应该是贪心&#xff0c;即我们每一次都去成各位中最大的那个数字。但是这个想法很容易推翻。因为你这次乘了一个最大的…

gdb/git的基本使用

热爱编程的你&#xff0c;一定经常徘徊在写bug和改bug之间&#xff0c;调试器也一定是你随影而行的伙伴&#xff0c;离开了它你应该会寝食难安吧&#xff01; 目录 gdb的使用 断点操作 运行调试 观察数据 Git的使用 仓库的创建和拉取 .gitignore “三板斧” 常用指令 gd…

CV——day82 读论文:遥感目标检测的改进注意力特征融合SSD (AF-SSD)方法

遥感目标检测的改进注意力特征融合SSD 方法I. INTRODUCTIONII. RELATED WORKB. 特征融合C.注意力机制III. PROPOSED METHODA. 特性融合模块——**FFM**B.双路径注意模块——DAMC. 多尺度接受域——MRFIV. EXPERIMENTSA. Data Sets and TrainingV. CONCLUSIONAttention and Feat…

mac安装开发工具:clipy、iterm2、go、brew、mysql、redis、wget等

wget brew install wget clipy Releases Clipy/Clipy GitHub 环境变量 ~下有三个文件 .zshrc .zprofile .bash_profile > cat .zshrc export PATH$PATH:/usr/local/mysql/bin> cat .zprofile eval "$(/opt/homebrew/bin/brew shellenv)"> cat .bas…

[1.3.2]计算机系统概述——中断和异常

文章目录第一章 计算机系统概述中断和异常&#xff08;一&#xff09;中断的作用&#xff08;二&#xff09;中断的类型&#xff08;三&#xff09;中断机制的基本原理小结第一章 计算机系统概述 中断和异常 中断的作用中断的类型 内中断&#xff08;也称“异常”&#xff09;…

29- 迁移学习 (TensorFlow系列) (深度学习)

知识要点 迁移学习: 使用别人预训练模型参数时&#xff0c;要注意别人的预处理方式。 常见的迁移学习方式&#xff1a; 载入权重后训练所有参数.载入权重后只训练最后几层参数.载入权重后在原网络基础上再添加一层全连接层&#xff0c;仅训练最后一个全连接层.训练数据是 10_m…

滚蛋吧,正则表达式!

大家好&#xff0c;我是良许。 不知道大家有没有被正则表达式支配过的恐惧&#xff1f;看着一行火星文一样的表达式&#xff0c;虽然每一个字符都认识&#xff0c;但放在一起直接就让人蒙圈了~ 你是不是也有这样的操作&#xff0c;比如你需要使用「电子邮箱正则表达式」&…

面试之String、StringBuffer、StringBuilder区别

String、StringBuffer、StringBuilder区别 (1)是否可变 string对象不可变&#xff1b; StringBuffer、StringBuilder继承自AbstractStringBuilder类&#xff0c;实现原理都基于可修改的char数组&#xff0c;默认大小为16 (2)线程安全性 string中的对象不可变&#xff0c;可…

Java中String类intern()详解

1、背景在开发过程中很多朋友&#xff0c;由于不会正确使用intern()&#xff0c;导致开发的程序&#xff0c;执行效率比较差。同时最近发现一道非常有意思的关于intern()的面试题&#xff0c;这道面试题还是有不小的难度&#xff0c;相信很多朋友看到以后也不知道怎么解答&…

c++类与对象整理(上)

目录 1.类的引入 2.类的定义 3.类的访问限定符及封装 1&#xff09;访问限定符 2&#xff09;封装 4.类的作用域 5.类的实例化 6.类的对象大小的计算 1&#xff09;类对象的存储方式 2&#xff09;内存对齐和大小计算 ​编辑 7.类成员函数的this指针 1&#xff09…

linux配置网络详解

linux配置网络详解 文章目录linux配置网络详解前置准备配置流程错误排查前置准备 确定是否有网&#xff0c;比如在家里&#xff0c;确定是否连上网线&#xff1f;确定这个网线的网关是什么&#xff1f;&#xff08;这个需要和给你办网的人确定&#xff09;&#xff0c;在公司的…