【TLPI读书笔记】 七、内存分配
在堆上分配内存
进程可以通过增加堆的大小来分配内存,所谓堆是一段长度可变的连续虚拟内存,始于进程的未初始化数据段末尾,随着内存的分配和释放而增减。通常将堆的当前内存边界称为“program break
”。
调整program break
:brk()
和sbrk()
改变堆的大小其实就是命令内核改变进程program break
的位置,最初program break
正好位于未初始化数据段末尾之后。
在program break
位置抬升后,程序可以访问新分配区域内的任何内存地址,而此时物理内存尚未分配。内核会在进程首次试图访问这些虚拟内存地址时自动分配新的屋里内存页。
传统UNIX系统提供了两个操纵program break
的系统调用:brk()
和sbrk()
,在Linux中依然可用:
#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
参数
end_data_segment
:指定program break
的位置,该参数实际会四舍五入到下一内存页边界处increment
:指定在program break
的原有地址上增加的大小(字节)
返回值
- 成功:
brk()
返回0;sbrk()
返回新分配的内存起始地址 - 失败:返回-1
- 成功:
在堆上分配内存:malloc()
和free()
一般情况下,C程序使用malloc()
函数族在堆上分配和释放内存,其建立在系统调用brk()
和sbrk()
之上,提供了更方便好用的内存分配方式。
malloc()
函数在堆上分配参数size字节大小的内存,并返回指向新分配内存起始位置处的指针,其所分配的内存未经初始化。
#include <stdlib.h>
void *malloc(size_t size);
// Returns 0 on success, or -1 on error
参数
size
:所需的内存大小(字节)
返回值
- 成功:返回分配内存的起始地址。因其返回类型为
void *
,所以可以赋给任意类型指针,但通常但做法是将其强制类型转换为符合指针变量的类型后进行赋值以便后续使用 - 失败:返回
NULL
,并设置errno
以返回错误信息。其原因一般是抵达program break
上限
- 成功:返回分配内存的起始地址。因其返回类型为
free()
函数释放ptr参数所指向的内存块。
#include <stdlib.h>
void free(void *ptr);
free()
函数内部原理是将指定地址的内存块添加至空闲内存块列表(该列表是一个双向链表)
参数
ptr
:内存地址,该参数应是由malloc()
函数(或其他malloc函数族函数)返回的地址。切记对一个内存地址释放一次后就不能再次释放该地址,否则将产生错误
在堆上分配内存的其他方法
函数calloc()
用于给一组相同对象分配内存。其所分配的内存会被初始化为0。
#include <stdlib.h>
void *calloc(size_t numitems, size_t size);
// Returns pointer to allocated memory on success, or NULL on error
参数
numitems
:指定分配对象的数量size
:指定每个对象的大小
返回值
- 成功:返回分配内存的起始地址
- 失败:返回
NULL
,并设置errno
以返回错误信息
realloc()
函数用来调整(通常是增加)一块内存的大小,而此块内存应是由malloc
包中函数所分配。
#include <stdlib.h>
void *realloc(void *ptr, size_t size);
// Returns pointer to allocated memory on success, or NULL on error
参数
ptr
:指定需要调整大小的内存块指针size
:指定所需调整的大小
返回值
- 成功:返回分配内存的起始地址(可能会与之前的位置发生不同,注意使用新返回的地址)
- 失败:返回
NULL
,并设置errno
以返回错误信息
分配对齐的内存:memalign()
和posix_memalign()
#include <malloc.h>
void *memalign(size_t boundary, size_t size);
// Returns pointer to allocated memory on success, or NULL on error
参数
boundary
:起始地址是该参数的整倍数,而该参数必须是2的整数次幂size
:所需的内存大小(字节)
返回值
- 成功:返回分配内存的起始地址(可能会与之前的位置发生不同,注意使用新返回的地址)
- 失败:返回
NULL
,并设置errno
以返回错误信息
#include <malloc.h>
int posix_memalign(void *memptr, size_t alignment, size_t size);
// Returns 0 on success, or a positive error number on error
参数
memptr
:已分配的内存地址通过该参数返回而不是由函数返回alignment
:起始地址是该参数的整倍数,而该参数必须是sizeof(void *)
与2的整数次幂两者间的乘积size
:所需的内存大小(字节)
返回值
- 成功:返回
0
- 失败:返回非
0
的错误号
- 成功:返回
在堆栈上分配内存:alloca()
alloca()
也可以动态分配内存,不过不是从堆上分配内存,而是通过增加栈帧的大小从堆栈上分配。根据定义,当前调用函数的栈帧位于堆栈的顶部,故而这种方法是可行的。因此,帧的上方存在扩展空间,只需修改堆栈指针即可。
#include <alloca.h>
void *alloca(size_t size);
// Returns pointer to allocated block of memory
参数
size
:指定在堆栈上分配的字节数
返回值
- 成功:返回分配内存的起始地址
- 失败:返回
NULL
,并设置errno
以返回错误信息
不需要(实际上也绝不能)调用free()
来释放由alloca()
分配的内存;同样也不能调用realloc()
来调整其内存大小。
总结
本章主要讲了内存分配,而内存又可以分为堆内存和堆栈内存,重点在堆内存分配。