在上一篇文章中,探讨了Linux系统对进程以及线程的内存分配问题,然后采用申请1KB内存的方式进行验证,然后发现将第二次申请的内存地址减去第一次申请内存的地址,长度为1040,比我们申请的1024多了16个字节,从而提出一个问题,“在64位系统中,为什么系统分配的内存比实际申请的内存大16个字节?”。
但是上一篇的文章主要是探讨内存分配的,所以对这个问题没有过多追究,提出这个问题也不严谨,因为只是申请了1024个字节,系统分配了1040,多了16个字节,那么如果申请1个字节、2个字节、3个字节等等,系统又是如何分配的,是否还是多分配16字节?64位系统和32位系统是否都是多分配16字节?
所以下面我们先验证下,如果我们申请不同大小的内存,系统是否还是会多申请16字节的内存?
#include
#include 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”为下一个申请的内存地址减去当前申请内存的地址,即为当前申请内存的大小。
在Ubuntu 64位系统下测试,结果如下:
在32位系统下测试,结果如下:
上面的数据,只是部分数据,经过综合,得出如下数据:
64位系统
32位系统
通过上面的表格,我们可以得出如下结论:
针对上面的结论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. Itmust be a power of two at least 2 * SIZE_SZ, even on machines forwhich smaller alignments would suffice. It may be defined as largerthan this though. Note however that code and data structures areoptimized 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进行确认。
针对问题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三个参数:
通过上图可以看到,一个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字段,剩下的便是可以使用的数据长度。