到目前为止数组的大小都是写死、编译期就知道的。但很多程序运行前根本不知道要多少空间,用户可能会输入任意长度的数据。动态内存分配(dynamic allocation)就是答案:运行时按需向系统申请内存,用完再归还。

为什么需要动态分配

固定数组的两个尴尬:空间常年不够用或太浪费。而动态分配让你:

  • 要多大给多大,不写死在代码里。
  • 不用的内存调用 free() 随时释放。
  • 也因此能搭出链表这类边吃边长的数据结构。

malloc 分配 + free 释放

从头到尾走一遍。假设有个 person 结构体:

typedef struct {
    char *name;
    int age;
} person;

给它动态开一块内存:

#include <stdlib.h>

person *myperson = (person *)malloc(sizeof(person));

拆开讲:

  • malloc(sizeof(person)),向系统申请"刚好塞下一个 person"的内存,返回指向这块区域的指针。
  • (person *),强制类型转换。malloc 返回的是 void *(无类型指针),转成 person * 才好用来给 myperson。其实 C 语言会自动帮你转,不写也能过,写上更明确。
  • sizeof 在这里不是真正调用的函数,编译器直接把它换算成 person 结构体占的字节数。

用起来和普通结构体指针一样,用 ->

myperson->name = "John";
myperson->age = 27;
printf("%s,%d 岁\n", myperson->name, myperson->age);

用完必须释放:

free(myperson);

⚠️ 一句话警钟:free 释放的是指针指向的这块内存,不是指针变量本身。free 之后,myperson 还指着那块已经归还的区域,这时的指针就叫悬垂指针,不能再碰。更稳健的处理方式是紧接着 myperson = NULL; 断干净。

malloc 可能失败

内存耗尽时 malloc 返回 NULL。好习惯是判一下再干活:

person *p = (person *)malloc(sizeof(person));
if (p == NULL) {
    printf("内存分配失败!\n");
    return 1;
}

动态分配数组:大小随便定

给数组动态开内存,元素数可以是运行时的变量:

C 语言 malloc 动态内存分配示例运行
#include <stdio.h>
#include <stdlib.h>

int main() {
    int n = 5;
    char *pvowels = (char *)malloc(n * sizeof(char));

    pvowels[0] = 'A';
    pvowels[1] = 'E';
    *(pvowels + 2) = 'I';   // 可以看到,下标和解引用混着用都行
    pvowels[3] = 'O';
    *(pvowels + 4) = 'U';

    for (int i = 0; i < n; i++) {
        printf("%c ", pvowels[i]);
    }
    printf("\n");

    free(pvowels);   // 用完归还
    return 0;
}

注意 pvowels 本质是指针,只是能当下标使,再次印证指针和数组用起来像,地位不同。

二维的动态分配:指针的指针

二维数组动态分配得用指向指针的指针:先给行指针数组开内存,再给每一行开内存。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int nrows = 2, ncols = 5;
    char **pvowels = (char **)malloc(nrows * sizeof(char *));

    for (int i = 0; i < nrows; i++) {
        pvowels[i] = (char *)malloc(ncols * sizeof(char));
    }

    pvowels[0][0] = 'A';  pvowels[0][1] = 'E';  pvowels[0][2] = 'I';
    pvowels[0][3] = 'O';  pvowels[0][4] = 'U';
    pvowels[1][0] = 'a';  pvowels[1][1] = 'e';  pvowels[1][2] = 'i';
    pvowels[1][3] = 'o';  pvowels[1][4] = 'u';

    for (int i = 0; i < nrows; i++) {
        for (int j = 0; j < ncols; j++) {
            printf("%c ", pvowels[i][j]);
        }
        printf("\n");
    }

    /* 释放顺序:先每行,再顶层 */
    for (int i = 0; i < nrows; i++) {
        free(pvowels[i]);
    }
    free(pvowels);
    return 0;
}

释放的顺序必须和分配相反:先 free 掉每一行,再 free 那一组行指针。顺序反了会漏内存。

内存泄漏:用多少还多少

每次 malloc 出来的内存都要用 free 还回去,忘了还就泄漏,程序一直跑,内存越占越多,某天直接崩。经验:谁申请,谁释放;尽早释放,绝不二次释放。 动态分配是 C 语言里最容易出事故的地方,下一篇的杨辉三角练习正好把它练熟。