栈上的数组与局部变量大小通常在编译期或进入作用域时即已确定。若程序在运行时才确定所需数据量(编写时尚不确定大小),比如用户输入要存放多少个整数、或根据文件中的记录条数决定缓冲区长度,可使用动态内存分配:在堆(heap)上按字节数申请存储,用指针保存返回地址,使用完毕后调用 free 归还。

为何需要动态分配

  • 元素个数或结构体数量可由用户输入、文件内容等运行时信息决定。
  • 生命周期可跨越函数调用,在不再需要时显式释放,避免长期占用。
  • 为链表、动态表格等“边用边扩”的结构提供存储基础。

malloc 与 free

以 person 结构体为例:

typedef struct {
    char *name;
    int age;
} person;
#include <stdlib.h>

person *myperson = (person *)malloc(sizeof(person));
  • malloc(sizeof(person)) 申请可容纳一个 person 的连续字节,成功时返回指向该区域的指针。
  • malloc 的返回类型为 void *,常转换为 person * 后再使用。
  • sizeof(person) 在编译期求得结构体所占字节数,用于指定申请大小。
myperson->name = "John";
myperson->age = 27;
printf("%s,%d 岁\n", myperson->name, myperson->age);

free(myperson);
myperson = NULL;

free 释放的是指针所指的堆内存,而非指针变量本身。释放后若继续通过原指针访问,可能形成悬垂指针;将指针置为 NULL 有助于避免误用。

检查分配是否成功

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

内存不足时 malloc 返回 NULL,使用前应判空。

动态分配一维数组

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

int main(void) {
    int n = 5;
    char *pvowels = (char *)malloc(n * sizeof(char));
    if (pvowels == NULL) {
        return 1;
    }

    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 是指向堆上连续 char 的指针,可用下标或指针算术访问,用法类似数组,但须自行记录长度并在适当时机 free。

动态分配二维结构

行数、列数在运行时确定时,可先分配“行指针数组”,再为每一行分配列存储:

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

int main(void) {
    int nrows = 2, ncols = 5;
    char **pvowels = (char **)malloc(nrows * sizeof(char *));
    if (pvowels == NULL) {
        return 1;
    }

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

    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;
}

释放顺序应与分配相反:先释放各行,再释放行指针数组。

内存泄漏与配对释放

每次成功的 malloc(或 calloc、realloc)都应有对应的 free;遗漏释放会导致内存泄漏。同一指针不要重复 free。动态内存与指针配合使用时,更需关注边界与生命周期。

本节介绍了堆上分配与释放的基本流程。malloc 返回 void *,其含义见void 指针一节。