0 概述

通常声明一个数组时需要使用一个常量来指定数组的长度,数组所占用的内存是在编译时就被分配。这种方式的声明的优点是简单,但是存在以下几个缺点:

  • 使用的元素数量超过数组声明的长度,当前数组就不能存储相应的数据;
  • 如果数组的长度被声明很大,实际使用的元素又比较少会导致内存空间的浪费;
  • 程序开发中会经常忽略对数组溢出异常的考虑。

所以就有了动态内存分配,即在程序运行时分配所需的内存,需要的时候就分配相应大小的内存使用,使用完毕后释放对应的内存。

1 动态内存分配与释放函数

1.1 malloc、calloc 和 realloc

C 函数库提供了三个动态内存分配的函数:malloc、calloc、realloc,三个函数原型如下所示:

#include <stdlib.h>

void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);

malloc 函数用于从内存池中提取一块合适的内存,并向当前程序返回一个指向该内存的指针,需要注意的是 malloc 所分配的内存并未初始化,使用之前最好手动初始化。

malloc 所分配的是一块连续的内存,其实际分配的内存有可能比传递的参数 size 稍微多一点(由编译器定义)。如果内存池为空,malloc 返回一个 NULL 指针,也就是说使用 malloc 函数时不一定每次都可以分配内存成功,所以对每个 malloc 的返回值都要进行检查,确保其返回值并非 NULL。

calloc 函数与 malloc 函数的主要区别是 calloc 会把分配的内存初始化为 0,只需向 calloc 传递所需元素的数量和每个元素的字节数。

realloc 函数用于修改原先已经分配的内存块大小,使用该函数可以对一块内存进行扩大或缩小。当扩大内存块时会将新增加的内存添加到原先的内存块后面(注意,新增加的内存未初始化);当缩小内存块时会从内存块的尾部缩减,保留其余部分的内存以及其中的内容。如果当前的内存块无法改变大小,realloc 分配正确大小的内存块并将原先内存中的内容复制到新分配的内存块中,所以在使用 realloc 函数后要用其返回新指针。当 realloc 的第一个参数为 NULL 时,此时 realloc 的功能和 malloc 一致。

1.2 free

当动态分配的内存不再需要使用时,此时就需要释放,这样这块内存之后可以被重新分配使用。free 用来释放 malloc、calloc 和 realloc 分配的内存空间,该函数原型如下所示:

#include <stdlib.h>

void free(void *ptr);

free 函数接收的参数要么为 NULL,要么为 malloc、calloc 和 realloc 三个函数返回的指针。使用 free 释放内存必须是整块一起释放,不能只释放一部分。

2 动态内存分配的正确姿势

动态内存分配的程序常常有很多错误,包括对 NULL 指针进行解引用、内存越界(操作内存时超出了分配内存的边界)、释放并非动态分配的内存、释放动态分配内存的一部分,又或是对已释放的内存再次进行操作等。这些错误都会导致当前程序 crash,下面给出一个相对不容易发生错误的内存分配器。

/* alloc.h */
#include <stdlib.h>

#define malloc
#define MALLOC(num, type) (type *)alloc( (num) * siezof(type) )
extern void *alloc(size_t size);

其中 #define malloc 用来防止其他代码块的代入导致偶尔直接调用 malloc,增加该定义,如果直接调用 malloc 在编译程序时会出现语法的错误,所以在 alloc.c 中需要使用 #undef 指令,保证在当前调用 malloc 不出错,具体实现如下代码所示:

#include <stdlib.h>
#include "alloc.h"
#undef malloc

void *alloc(size_t size)
{
    void *new_mem;
    
    new_mem = malloc(size);
    if (new_mem == NULL) {
        fprintf(stderr, "Out of memory!\n");
        exit(1);
    }
    return new_mem;
}

3 总结

  • 使用 malloc 函数分配空间时需要检查其返回的指针是否为 NULL;
  • malloc 分配的内存空间是一个连续的块;
  • free 分配的空间需要传递 malloc、calloc 和 realloc 函数返回的指针,亦或者 NULL;
  • malloc 需要 和 free 搭配使用,即分配了内存空间后,该内存空间不再使用时需手动使用 free 释放,防止内存泄漏;
  • 需要注意的是,不能再访问已经释放的内存。