在堆上分配内存

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

调整program breakbrk()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()来调整其内存大小。

总结

本章主要讲了内存分配,而内存又可以分为堆内存和堆栈内存,重点在堆内存分配。

标签: Linux, C/C++, TLPI

添加新评论