内存分配.
内存分配
- 一、在堆上分配内存
- 1.调整program break:brk和sbrk
- 2.在堆上分配内存:malloc()和free
- 3.调用free还是不调用
- 4.malloc和free的实现
- 5.在堆上分配内存其他方法
- 6.分配对齐的内存
- 二、在堆栈上分配内存
一、在堆上分配内存
进程可以通过增加堆的大小来分配内存,所谓堆是一段长度可变的连续虚拟内存,始于进程的未初始化数据段末尾,随着内存的分配和释放而增减。将堆的当前内存边界称为"program break"
。
1.调整program break:brk和sbrk
在program break
的位置抬升后,程序可以访问新内存区域内的任何内存地址
,而此时物理内存页尚未分配
。内核会在进程首次试图访问这些虚拟内存地址时自动分配新的物理内存页。
#include<unistd.h>
int brk(void *end_data_segment);//将program break设置指定位置
//设置低于初始化值结果难以预料。
void *sbrk(intptr_t increment);//在原有地址上增加从increment传入的大小。
2.在堆上分配内存:malloc()和free
malloc优点:
- 更易于在多线程程序中使用;
- 接口简单,允许分配小块内存;
- 允许随意释放内存块,被维护于一张空闲内存表中,后续内存分配调用循环使用。
//在堆上分配大小为size的内存,并返回指向新分配内存起始位置处的指针,其所分配的内存未经初始化。
#include<stdlib.h>
void *malloc(size_t size);
-
malloc函数返回类型为
void*
,所以可以分配任意类型的C指针。 -
malloc函数返回内存所采用的字节对齐方。
-
无法分配内存返回NULL,设置errno返回错误信息。
//释放ptr所指向的内存块,由malloc返回的地址
#include<stdlib.h>
void free(void *ptr);
//传入是空指针,则什么都不做。
free并不降低program break的位置,而是将这块内存添加到空闲内存列表中,供后续的malloc函数循环使用,原因:
-
被释放的内存通常会位于堆的中间,而非堆的顶部。
-
最大限度地减少程序必须执行的sbrk调用次数。
-
在多数情况,降低program break的位置不会对那些分配大量内存的程序有多少帮助。因为通常倾向于持有已分配或是反复释放和重新分配内存,而非释放所有内存后持续一段时间。
3.调用free还是不调用
-
进程终止时,其占用的所有内存都会
返还
给操作系统,包括堆内存中由malloc函数包含一系列函数所分配的内存。-
基于内存自动释放机制,对于那些分配了内存并在进程终止前持续使用的程序而言,通常会省略对free的调用。
- 依靠指针进程自动释放内存可以接受,但基于以下原因,最好显示释放已分配的内存:
- 显示调用free函数能使程序在未来修改时更具有可读性和可维护性。
- 若使用malloc调试库来查找程序内存泄漏问题,则将任何未经显示释放处理的内存报告为内存泄漏,使工作复杂化。
-
4.malloc和free的实现
malloc实现:
-
首先扫描之前由free所释放的空闲内存块列表,寻求合适的空闲内存块。
-
内存块合适直接返回给调用者,更大内存则将其分割,在将一块合适内存返回给调用者,把小的留在空闲列表中。
-
若内存无足够大空闲内存块,那么malloc调用sbrk分配内存。
free实现:
-
malloc分配内存块时,会额外分配字节几个来存放记录这块内存大小的整数值。该整数位于内存块的起始处,而实际返回给调用者的内存地址恰好位于这一长度记录字节之后。
-
当将内存置于空闲内存列表时,free使用内存块本身的空间来存放链表指针,将自身添加到列表中
-
随着对内存不断释放和重新分配空闲列表中的空闲内存会和已分配的在用内存混杂在一起
分配和释放时注意以下规则:
-
分配一块内存后,不要改变这块内存范围外的任何内容,错误的指针运算。
-
释放同一块已分配内存超过一次的错误。
-
若非经由malloc函数包中函数所指向的指针,绝对不能在调用free函数时使用。
-
反复分配内存,确保释放所有已使用完毕的内存。
5.在堆上分配内存其他方法
//分配内存
#include<stdio.h>
void *calloc(size_t numitems,size_t size);
//numitems:数量
//size:大小
//成功:返回指向这块内存起始处的指针,无法分配NULL
//将已分配的内存初始化为0
//free释放
struct {}myStruct;
struct myStruct *p;
p = calloc(1000,sizeof(struct myStruct));
if(p==NULL)
{...}
//调整一块内存大小,此块内存由malloc分配的
#include<stdlib.h>
void *realloc(void *ptr,size_t size);
//ptr:指向需要调整大小的内存块指针。
//size:大小期望值。
//成功:返回指向大小调整后内存块的指针,错误NULL。
//增加已分配内存块的大小,则不会对额外分配的字节进行初始化。
free释放
//重新定位由ptr指向的内存块
nptr = realloc(ptr,newsize);
if(nptr==NULL){}else{ptr=nptr;}
6.分配对齐的内存
分配内存起始位置要与2的整数次幂对齐。
#include<malloc.h>
void *memalign(size_t boundary,size_t size);
//分配size个字节内存
//起始位置参数boundary的整数倍
//返回已分配内存的地址。
//free释放
int posix_memalign(void **memptr,size_t alignment,size_t size);
//出错返回错误号
//free释放
int s;
void *memptr;
s = posix_memalign(&memptr,1024*sizeof(void *),65536);
memalign和 posix_memalign
不同之处:
-
已分配的内存地址通过参数memptr返回。
-
内存与
aligment
参数的整数倍对齐,aligment
必须是sizeof(void*)
与2的整数次幂两两者的乘积。
二、在堆栈上分配内存
-
allcoa
动态分配,不是从堆上分配内存,而是通过增加栈帧大小从堆栈上分配。 -
当调用函数的栈帧位于堆栈的顶部,方法可行。帧的上方存在扩展空间,只需修改堆栈指针值即可。
#include<alloca.h>
void *alloca(size_t size);
//size:指定堆栈上分配的字节数。
//返回值为已分配内存块的指针
//不能使用fee释放和不能使用realloc调整内存大小。
//调用alloca造成堆栈溢出,则程序的行为无法预知,特别没有收到NULL返回值通知错误情况下。
void *y;
y = alloca(size);
func(x,y,z);
allcoa分配内存相对于malloc有一定优势:
- 速度快;
- alloca分配的内存随栈帧的移除自动释。