内存分配

原创 2015年11月20日 17:19:37

我们已经知道,每一个进程内存包含多个段,如文本段、已初始化数据段、未初始化数据段和堆栈段等。程序中已初始化的全局变量和静态变量会在已初始化数据段中,而未初始化的数据在未初始化数据段中。对于这些数据,程序运行的时候才分配内存,这里的分配内存,是由系统程序完成的。

系统程序需要为动态数据结构(例如链表和二叉树等)分配额外的内存,这类的数据结构的大小由运行时所获取的信息决定。内存分配可以在堆或堆栈上进行。

下图是进程内存布局图:



1、在堆上分配内存

图中,堆是一段长度可变的连续虚拟内存,开始于未初始化数据段的末尾,随着内存的分配和释放而增减。可以通过改变堆的大小来实现内存的分配。通常将堆的当前内存边界称为“program break”。

C语言中可以使用malloc()实现内存分配,malloc是基于brk()和sbrk()系统调用实现的。brk和sbrk可以调整program break,由于program break就是未使用内存和堆之间的界限,因此修改program break就是改变堆的大小。最开始的时候,program break在未初始化数据段的末尾,和&end位置相同。

需要注意的是,分配的内存是虚拟内存,并没有分配实际物理内存,内核会在进程首次访问这些虚拟内存时自动分配新的物理内存分页。下面是brk和sbrk系统调用的具体格式:

#include <unistd.h>
int brk(void *end_data_segment);//Returns 0 on success,or -1 on error
void *sbrk(intptr_t increment);//Returns previous program break on success,or (void *)-1 on error

brk将program break设置为参数end_data_segment,注意,虚拟内存以页为单位进行分配,end_data_segment会和下一个内存页的边界处对齐。同时还要注意,当试图将program break设置为比当前值小的位置时,可能会发生意想不到的错误。

sbrk系统调用将program break增加参数increment大小。intptr_t是正数数据类型。如果调用成功,sbrk返回前一个program break的地址。也就是说,如果增加program break,那么sbrk将返回新分配的内存的起始位置的指针。

小技巧:可以调用sbrk(0)得到当前program break的位置。

C语言中的内存分配函数malloc和内存释放函数free是基于brk和sbrk系统调用实现的。下面是具体格式:

#include <stdlib.h>
void *malloc(size_t size);//Returns pointer to allocated memory on success,or NULL on error
void free(void *ptr);
malloc函数返回新分配内存的起始位置的指针,如果无法分配内存,则返回NULL;free函数释放参数ptr指向的内存块。

由于以下的原因,free函数通常不降低program break的位置:

  • 被释放的内存块通常会位于堆的中间,而非堆的顶部;
  • 这样减少了sbrk系统调用次数;
  • 在大多数情况下,降低program break的位置不会对那些分配大量内存的程序有多少帮助,因为它们通常倾向于持有已分配内存或是反复释放和重新分配内存,而非释放所有内存后再持续运行一段时间。
下面的程序说明了free对program break的影响。程序有多个参数,第一个参数是分配内存的块数,第二个参数是每个内存块的大小,第三个参数指定了释放内存块的步长,第四个和第五个参数限定了释放内存块的范围,如果不指定,就当全部范围。
#include <unistd.h>
#include <stdlib.h>
#include "error_functions.c"
#include "get_num.c"
#define MAX_ALLOCS 1000000
int main(int argc,char *argv[])
{
	if(argc<3||strcmp(argv[1],"--help")==0)
		usageErr("%s num_blocks block_size [step [min [max]]]\n",argv[0]);
	char *ptr[MAX_ALLOCS];
	int freeStep,freeMin,freeMax,blockSize,blockNum;
	blockNum=getInt(argv[1],GN_GT_0,"block_num");
	if(blockNum>MAX_ALLOCS)
		errExit("block_num>MAX_ALLOCS\n");
	blockSize=getInt(argv[2],GN_GT_0|GN_ANY_BASE,"block_size");
	freeStep=(argc>3)?getInt(argv[3],GN_GT_0,"step"):1;
	freeMin=(argc>4)?getInt(argv[4],GN_GT_0,"min"):1;
	freeMax=(argc>5)?getInt(argv[5],GN_GT_0,"max"):blockNum;
	if(freeMax>blockNum)
		errExit("bigger");
	printf("Initial program break:    %10p\n",sbrk(0));
	int j=0;
	for(j=0;j<blockNum;j++)
	{
		if((ptr[j]=malloc(blockSize))==NULL)
			errExit("malloc");
	}
	printf("Program break is now:     %10p\n",sbrk(0));
	for(j=freeMin-1;j<freeMax;j+=freeStep)
		free(ptr[j]);
	printf("After free(),program break is:%10p\n",sbrk(0));
	exit(EXIT_SUCCESS);
}

下面给出几个运行示例:


(1)分配1000个内存块,每个10240字节,步长是1,全部释放
可以看到,当释放的内存足够大的时候,free会降低program break;
(2)连续释放前999个内存块
结果program break没有变化,因为释放的内存块在堆的中间;
(3)每隔一个内存块释放一个
program break也没有变化
(4)释放后500块内存块
program break下降了,free使用sbrk系统调用实现。
free函数会在释放是将相邻的内存块合并为一个连续的内存块。
2、在堆栈上分配内存
alloca函数通过增加栈帧的大小来分配内存。格式如下:
#include <alloca.h>
void *alloca(size_t size);//Returns pointer to allocated block of memory
alloca函数有几个优点,一个就是速度快,因为编译器将它作为内联代码处理。还有,alloca分配的内存会随栈帧的移除而自动释放,即当调用alloca的函数返回时。这是因为函数返回时所执行的代码会重置栈指针寄存器,使其指向前一帧的末尾。

版权声明: 举报

相关文章推荐

返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)