010-C语言动态内存

plana_lab 发布于 17 天前 98 次阅读



前面我们学习了:

数组
函数
指针
结构体

之前创建变量时,通常都是直接写:

int a;
int arr[10];
struct Student s;

这些变量的大小通常在程序编译时就已经确定。

但是有时候我们并不知道程序运行时到底需要多少内存。

例如:

用户输入需要保存多少个整数?
学生到底有多少人?
文件有多大?
字符串到底有多长?

这时候就需要使用:

动态内存分配。


一、什么是动态内存

普通变量:

int a;

程序运行时,会自动为 a 分配内存。

而动态内存是:

程序运行
   ↓
需要多少内存
   ↓
运行时申请
   ↓
使用
   ↓
不需要了
   ↓
释放

这就是:

动态内存分配。

C 语言主要使用下面几个函数:

malloc
calloc
realloc
free

二、栈内存和堆内存

学习动态内存之前,先认识两个概念。

栈(Stack)

例如:

int a = 10;

void test() {
    int x = 20;
}

这种普通局部变量通常使用栈空间。

可以简单理解为:

进入函数
    ↓
创建局部变量
    ↓
使用
    ↓
函数结束
    ↓
自动释放

例如:

void test() {
    int x = 10;
}

test() 结束后,x 就不再存在。


三、堆(Heap)

动态申请的内存通常来自堆。

例如:

int *p = malloc(sizeof(int));

可以理解成:

程序运行
   ↓
malloc
   ↓
从堆中申请内存
   ↓
返回地址
   ↓
p 保存地址

这块内存不会因为普通函数结束就自动释放。

需要手动:

free(p);

四、为什么需要动态内存

假设我们需要保存 100 个整数。

最简单的方法:

int arr[100];

但是如果用户运行程序后输入:

需要 100000 个整数

那么程序就不能继续使用:

int arr[100];

因为大小已经写死了。

可以使用动态内存:

int n;

scanf("%d", &n);

int *arr = malloc(sizeof(int) * n);

此时:

n = 10
↓
申请 10 个 int

n = 100
↓
申请 100 个 int

n = 100000
↓
申请 100000 个 int

这就是动态内存的意义。


五、malloc

malloc 是最常用的动态内存分配函数。

需要:

#include <stdlib.h>

基本格式:

malloc(字节数);

例如:

int *p = malloc(sizeof(int));

这里申请了足够存放一个 int 的内存。


六、malloc 返回什么

这是最重要的地方之一。

malloc(sizeof(int))

返回的是:

这块内存的地址。

所以可以:

int *p = malloc(sizeof(int));

也就是:

malloc
  ↓
申请内存
  ↓
得到地址
  ↓
p 保存地址

可以理解成:

p
↓
┌──────────┐
│ int 内存 │
└──────────┘

七、malloc + sizeof

不要直接假设 int 一定是 4 字节。

推荐:

int *p = malloc(sizeof(int));

而不是:

int *p = malloc(4);

因为:

sizeof(int)

会自动得到当前平台的 int 大小。


八、申请多个元素

例如申请 10 个 int:

int *p = malloc(sizeof(int) * 10);

可以理解成:

p
↓
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│int │int │int │int │int │int │int │int │int │int │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

这和数组:

int arr[10];

在使用上有很多相似之处。

例如:

p[0] = 10;
p[1] = 20;
p[2] = 30;

九、动态数组

一个完整例子:

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

int main() {
    int n;

    printf("请输入数量: ");
    scanf("%d", &n);

    int *arr = malloc(sizeof(int) * n);

    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }

    free(arr);

    return 0;
}

例如输入:

5

输出:

1 2 3 4 5

这里:

int *arr = malloc(sizeof(int) * n);

就创建了一个运行时决定大小的“数组”。


十、malloc 申请失败

malloc 不一定总能成功。

如果申请失败,它会返回:

NULL

例如:

int *p = malloc(sizeof(int) * 100);

if (p == NULL) {
    printf("内存申请失败\n");
    return 1;
}

因此推荐:

int *p = malloc(sizeof(int));

if (p == NULL) {
    printf("malloc failed\n");
    return 1;
}

十一、NULL 是什么

NULL 可以理解为:

空指针。

例如:

int *p = NULL;

表示:

p 目前没有指向有效对象

所以在使用 malloc 后,经常写:

if (p == NULL) {
    ...
}

检查申请是否成功。


十二、使用动态内存

例如:

int *p = malloc(sizeof(int));

if (p == NULL) {
    return 1;
}

*p = 100;

printf("%d\n", *p);

free(p);

这里把之前学的指针全部串起来了。


十三、malloc 的完整流程

动态内存最重要的流程:

申请
 ↓
malloc
 ↓
检查 NULL
 ↓
使用
 ↓
free

例如:

int *p = malloc(sizeof(int));

if (p == NULL) {
    return 1;
}

*p = 100;

printf("%d\n", *p);

free(p);

可以记成:

malloc → 使用 → free

十四、free

申请了动态内存以后,使用结束就需要释放:

free(p);

例如:

int *p = malloc(sizeof(int));

*p = 100;

printf("%d\n", *p);

free(p);

free() 的作用:

释放之前动态申请的那块内存。


十五、为什么需要 free

假设:

void test() {
    int *p = malloc(sizeof(int) * 1000);
}

函数结束以后:

p 这个指针变量消失

但是:

malloc 得到的堆内存

仍然可能没有被释放。

这就产生了:

内存泄漏。


十六、内存泄漏

例如:

int *p = malloc(sizeof(int));

p = NULL;

这里原来的地址丢掉了。

变成:

malloc
 ↓
一块内存
 ↓
p 指向它

p = NULL

原来的地址丢失
 ↓
没人能找到这块内存

这就是典型的内存泄漏。

正确方式:

int *p = malloc(sizeof(int));

free(p);

p = NULL;

十七、free 后指针会怎样

看:

int *p = malloc(sizeof(int));

*p = 100;

free(p);

注意:

free(p)

释放的是:

p 指向的内存。

并不是让:

p

这个指针变量自动变成 NULL。

所以 p 可能仍然保存着原来的地址。


十八、野指针 / 悬空指针

例如:

int *p = malloc(sizeof(int));

*p = 100;

free(p);

printf("%d\n", *p);

这是错误的。

因为:

p
 ↓
原来的内存
 ↓
已经 free

此时 p 还保存着地址,但那块内存已经不能再使用。

这种情况通常称为:

悬空指针(dangling pointer)

也常被归入大家常说的“野指针”问题。


十九、free 后置 NULL

一个常见的安全习惯:

free(p);
p = NULL;

这样:

free
 ↓
释放内存

p = NULL
 ↓
不再指向旧地址

之后可以检查:

if (p == NULL) {
    printf("p 没有指向有效内存\n");
}

二十、不能重复 free

例如:

int *p = malloc(sizeof(int));

free(p);
free(p);

这是错误的。

同一块动态内存不能随意释放两次。

这种问题叫:

double free

更安全的写法:

free(p);
p = NULL;

第二次如果:

free(p);

因为 p 已经是 NULL,通常不会再次释放刚才那块内存。

不过更重要的是:

管理好每块内存的所有权和生命周期。


二十一、动态内存与数组

例如:

int *arr = malloc(sizeof(int) * 5);

可以像数组一样:

arr[0] = 10;
arr[1] = 20;
arr[2] = 30;

也可以使用指针:

*(arr + 0) = 10;
*(arr + 1) = 20;
*(arr + 2) = 30;

这和之前学习的数组、指针完全联系起来了。

因为:

arr[i]

本质上就是:

*(arr + i)

二十二、malloc + for

例如动态创建一个数组:

int n = 5;

int *arr = malloc(sizeof(int) * n);

if (arr == NULL) {
    return 1;
}

for (int i = 0; i < n; i++) {
    arr[i] = (i + 1) * 10;
}

for (int i = 0; i < n; i++) {
    printf("%d ", arr[i]);
}

free(arr);

输出:

10 20 30 40 50

二十三、calloc

除了 malloc,还有:

calloc

基本格式:

calloc(元素数量, 每个元素大小);

例如:

int *arr = calloc(5, sizeof(int));

表示:

5 个 int

和:

malloc(sizeof(int) * 5)

类似。

一个重要区别是:

calloc 分配的内存会被初始化为零。

例如:

int *arr = calloc(5, sizeof(int));

通常可以直接得到:

0 0 0 0 0

二十四、malloc 和 calloc

可以简单记:

malloc
↓
申请内存
↓
内容未初始化

calloc
↓
申请内存
↓
初始化为 0

例如:

int *a = malloc(sizeof(int) * 5);

里面原本是什么值,不应该依赖。

而:

int *b = calloc(5, sizeof(int));

得到的元素会初始化为 0。


二十五、realloc

如果已经申请了一块动态内存,但是后来发现:

不够用了

可以使用:

realloc

例如:

int *p = malloc(sizeof(int) * 5);

p = realloc(p, sizeof(int) * 10);

原本:

5 个 int

变成:

10 个 int

二十六、realloc 的作用

可以理解成:

malloc
 ↓
申请 5 个元素

使用

发现不够
 ↓
realloc
 ↓
调整空间大小

它可以:

扩大

也可以:

缩小

动态数组经常会使用它。


二十七、realloc 的一个重要问题

不要直接习惯性写:

p = realloc(p, new_size);

因为如果 realloc 失败:

realloc 返回 NULL

那么原来的地址可能就丢失了。

更安全的写法:

int *temp = realloc(p, new_size);

if (temp == NULL) {
    printf("realloc failed\n");
} else {
    p = temp;
}

也就是:

旧指针 p
   ↓
realloc
   ↓
临时指针 temp
   ↓
检查成功
   ↓
p = temp

这是一种很重要的动态内存编程习惯。


二十八、realloc 示例

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

int main() {

    int *arr = malloc(sizeof(int) * 5);

    if (arr == NULL) {
        return 1;
    }

    for (int i = 0; i < 5; i++) {
        arr[i] = i + 1;
    }

    int *temp = realloc(arr, sizeof(int) * 10);

    if (temp == NULL) {
        free(arr);
        return 1;
    }

    arr = temp;

    for (int i = 5; i < 10; i++) {
        arr[i] = i + 1;
    }

    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }

    free(arr);

    return 0;
}

输出:

1 2 3 4 5 6 7 8 9 10

二十九、动态内存和结构体

现在把上一章的结构体接进来。

例如:

struct Student {
    char name[50];
    int age;
    double score;
};

可以动态申请一个:

struct Student *p;

然后:

p = malloc(sizeof(struct Student));

这样就在堆上申请了一个 Student 大小的空间。


三十、结构体 + malloc

完整例子:

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

struct Student {
    char name[50];
    int age;
    double score;
};

int main() {

    struct Student *p = malloc(sizeof(struct Student));

    if (p == NULL) {
        return 1;
    }

    p->age = 18;
    p->score = 95.5;

    printf("age = %d\n", p->age);
    printf("score = %.2f\n", p->score);

    free(p);
    p = NULL;

    return 0;
}

这里同时使用了:

结构体
+
结构体指针
+
malloc
+
free
+
->

三十一、动态创建结构体数组

还可以一次申请多个结构体:

struct Student *students =
    malloc(sizeof(struct Student) * 100);

表示:

100 个 Student

然后:

students[0].age = 18;
students[1].age = 19;
students[2].age = 20;

或者:

(students + 0)->age = 18;
(students + 1)->age = 19;

这里又和之前的:

数组
指针
结构体

全部连接起来了。


三十二、typedef + malloc

如果之前使用了:

typedef struct {
    char name[50];
    int age;
    double score;
} Student;

那么可以写:

Student *p = malloc(sizeof(Student));

而不是:

struct Student *p = malloc(sizeof(struct Student));

例如:

Student *p = malloc(sizeof(Student));

if (p == NULL) {
    return 1;
}

p->age = 18;

free(p);
p = NULL;

三十三、动态字符串

动态内存不只可以保存数字。

例如字符串:

char *str = malloc(sizeof(char) * 100);

然后:

scanf("%99s", str);

使用结束:

free(str);

也可以写:

char *str = malloc(100);

因为 char 通常占 1 字节。

但更加推荐明确表达意图:

char *str = malloc(sizeof(char) * 100);

三十四、动态二维数组

简单情况下,也可以申请一整块连续内存:

int rows = 3;
int cols = 4;

int *arr = malloc(sizeof(int) * rows * cols);

访问:

arr[i * cols + j]

例如:

arr[0 * cols + 0]
arr[0 * cols + 1]
arr[1 * cols + 0]

可以把二维数据放在一维的连续内存里。

后面学习更复杂的数据结构时,这种思想会非常常见。


三十五、sizeof 与动态内存

例如:

int *p = malloc(sizeof(int) * 10);

注意:

sizeof(p)

和:

sizeof(*p)

不是一回事。

p 是指针:

sizeof(p)

得到的是:

指针本身的大小。

而:

sizeof(*p)

得到的是:

p 指向的数据类型大小。

例如:

int *p;

那么:

sizeof(p)

是指针大小。

而:

sizeof(*p)

是 int 的大小。

因此动态数组申请时:

malloc(sizeof(*p) * n);

也是一种很实用的写法。


三十六、动态内存和普通数组的区别

普通数组:

int arr[10];

特点:

大小通常在定义时确定
生命周期由作用域等规则管理
不需要手动 free

动态数组:

int *arr = malloc(sizeof(int) * n);

特点:

大小可以运行时决定
生命周期由程序员管理
需要 free

可以简单理解:

普通数组
↓
编译/定义时决定大小

动态内存
↓
运行时决定大小

三十七、常见错误

1. 没有检查 malloc

不推荐:

int *p = malloc(sizeof(int));

*p = 10;

更安全:

int *p = malloc(sizeof(int));

if (p == NULL) {
    return 1;
}

*p = 10;

2. 使用未初始化的 malloc 内存

例如:

int *p = malloc(sizeof(int));

printf("%d\n", *p);

不能依赖这里是什么值。

应该先写入:

*p = 10;

再读取。


3. 忘记 free

例如:

int *p = malloc(sizeof(int));
*p = 10;

如果不再使用,却没有:

free(p);

就可能导致内存泄漏。


4. free 后继续使用

错误:

free(p);

printf("%d\n", *p);

释放后不要继续访问。


5. 重复 free

错误:

free(p);
free(p);

不要重复释放同一块内存。


6. 越界

例如:

int *p = malloc(sizeof(int) * 5);

合法:

p[0]
p[1]
p[2]
p[3]
p[4]

错误:

p[5]

因为你只申请了 5 个元素。


三十八、动态内存的生命周期

可以把动态内存理解成:

申请
 ↓
malloc / calloc
 ↓
检查
 ↓
使用
 ↓
realloc(需要调整时)
 ↓
继续使用
 ↓
free
 ↓
结束

例如:

int *p = malloc(sizeof(int) * 10);

if (p == NULL) {
    return 1;
}

/* 使用 p */

int *temp = realloc(p, sizeof(int) * 20);

if (temp != NULL) {
    p = temp;
}

/* 继续使用 p */

free(p);
p = NULL;

三十九、一个完整的小程序

实现:

用户输入人数
↓
动态创建学生数组
↓
输入学生信息
↓
输出学生信息
↓
释放内存

代码:

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

typedef struct {
    char name[50];
    int age;
    double score;
} Student;

int main() {

    int n;

    printf("请输入学生数量: ");
    scanf("%d", &n);

    Student *students =
        malloc(sizeof(Student) * n);

    if (students == NULL) {
        printf("内存申请失败\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        printf("请输入第 %d 个学生的信息:\n", i + 1);

        printf("姓名: ");
        scanf("%49s", students[i].name);

        printf("年龄: ");
        scanf("%d", &students[i].age);

        printf("成绩: ");
        scanf("%lf", &students[i].score);
    }

    printf("\n学生信息:\n");

    for (int i = 0; i < n; i++) {
        printf(
            "%s %d %.2f\n",
            students[i].name,
            students[i].age,
            students[i].score
        );
    }

    free(students);
    students = NULL;

    return 0;
}

这个例子已经把:

数组
指针
结构体
函数
动态内存

开始真正组合起来了。


四十、本章核心知识

这一章最需要掌握的不是所有函数,而是下面这条思路:

指针保存地址
        ↓
malloc 在运行时申请内存
        ↓
指针指向这块内存
        ↓
通过指针使用内存
        ↓
不需要了
        ↓
free 释放内存

最核心的代码:

int *p = malloc(sizeof(int));

if (p == NULL) {
    return 1;
}

*p = 100;

printf("%d\n", *p);

free(p);
p = NULL;

以及动态数组:

int *arr = malloc(sizeof(int) * n);

四十一、练习

练习 1

使用 malloc 动态申请一个 int:

输入一个数字
↓
保存到动态内存
↓
输出
↓
free

练习 2

让用户输入 n:

n = ?

然后动态申请:

n 个 int

使用 for 输入所有数字,再全部输出。


练习 3

使用:

calloc

动态创建 10 个 int,观察它们初始值。


练习 4

使用:

realloc

先创建 5 个整数,再扩展成 10 个整数。


练习 5

写一个:

void double_array(int *p, int n)

动态申请数组后:

1 2 3 4 5

调用函数变成:

2 4 6 8 10

练习 6

使用:

typedef struct

定义 Student。

然后动态申请一个学生:

Student *p = malloc(sizeof(Student));

通过:

p->age

修改年龄。

最后:

free(p);

练习 7

动态创建 n 个学生:

Student *students =
    malloc(sizeof(Student) * n);

输入并输出全部学生信息。

这是本章最重要的综合练习之一。


此作者没有提供个人介绍。
最后更新于 2026-09-18