【C语言】动态内存管理

目录

引言

一、内存空间开辟

1.malloc

①函数原型

②函数行为

③使用示例

④使用细节

2.calloc

①函数原型

②函数行为

③使用示例

④使用细节

3.realloc

①函数原型

②函数行为

③使用示例

④使用细节

二、内存空间释放

1.free

①函数原型

②函数行为

③使用示例

④使用细节


引言

许多实际场景中,程序需要的空间大小只有在运行时才能知道(比如读取用户输入、处理可变长度的数据、构建链表/树等数据结构)。如果仅靠静态数组或局部变量,要么造成空间浪费(分配过大),要么导致溢出(分配过小)。

动态内存管理让程序员可以在堆上按需申请和释放内存,赋予程序极大的灵活性,同时也能精确控制对象的生命周期,避免全局/静态变量的持久开销。

C标准库提供了四个核心的动态内存管理函数,都声明在 <stdlib.h> 中:

一、内存空间开辟

1.malloc

①函数原型

复制代码
#include <stdlib.h>
void* malloc(size_t size);
  • 参数size ------ 请求分配的字节数 ,类型为 size_t(通常是无符号整型)。

  • 返回值

    • 成功:返回一个指向至少 size 字节的连续内存块首地址的指针。该指针已对齐,可安全地转换为任何类型的指针。

    • 失败:返回 NULL

  • 注意:返回的指针需要使用者自行显式转换为目标类型(在 C 中可选,但推荐)。

②函数行为

  1. 在进程的(heap)上分配一块指定大小的连续内存。

  2. 不初始化 :分配的内存内容是未定义的 (可能是之前的残留数据),如需零值初始化请使用 calloc

  3. 分配的内存块独立于程序的其他存储区(栈、静态区),其生命周期由程序员显式控制:从分配成功到被 free() 释放,或直至程序结束。

③使用示例

复制代码
#include <stdlib.h>

// 分配 10 个 int 的数组
int main()
{
    int *arr = malloc(10 * sizeof(int));
    if (!arr) 
    {
        perror("malloc failed");
        exit(EXIT_FAILURE);
    }
    // 使用 arr[0] 到 arr[9] ...
    // ...
    free(arr);
    return 0;
}

④使用细节

  • 使用前必须检查返回值

    复制代码
    int *p = malloc(1000);
    *p = 42;               // 如果 p == NULL,立即崩溃
  • 建议始终使用 sizeof 约束使用的字节数,避免假设类型大小

    复制代码
    // 错误:假设 int 为 4 字节
    int *arr = malloc(10 * 4);
    // 正确
    int *arr = malloc(10 * sizeof(int));
    // 更保险:用指针所指向的类型
    int *arr = malloc(10 * sizeof *arr);
  • 不要弄丢原始指针指向的位置

    复制代码
    int *p = malloc(10);
    p++;                   // 指针偏移
    free(p);               // 未定义行为!
  • 空间使用完毕后及时 free ,防止内存泄漏

    复制代码
    void func() 
    {
        int *p = malloc(100);
        if (err) return;   // 忘记 free(p) → 泄漏
        free(p);
    }

2.calloc

①函数原型

复制代码
#include <stdlib.h>
void* calloc(size_t num, size_t size);
  • 参数

    • num:要分配的元素个数。

    • size:每个元素的大小(字节)。

  • 返回值

    • 成功时返回指向分配内存的指针(已对齐,适合任何类型);

    • 失败时返回 NULL

  • 注意:返回的指针需要使用者自行显式转换为目标类型(在 C 中可选,但推荐)。

②函数行为

  1. 计算总分配大小 = num * size

  2. 上分配一块连续内存,至少能容纳该大小。

  3. 将这块内存的每一个字节都初始化为 0

③使用示例

复制代码
#include <stdlib.h>

int main()
{
    // 分配包含 100 个 double 的数组,全部为 0.0
    double *arr = calloc(100, sizeof(double));
    if (!arr) 
    {
        // 处理失败
    }
    // 使用 arr ...
    free(arr);
    return 0;
}

④使用细节

  • 使用前必须检查返回值
  • 不要弄丢原始指针指向的位置
  • 空间使用完毕之后及时 free ,防止内存泄漏

3.realloc

①函数原型

复制代码
#include <stdlib.h>
void* realloc(void *ptr, size_t new_size);
  • 参数

    • ptr:指向之前由 malloccallocrealloc 返回的内存块的指针。如果为 NULL,行为等同于 malloc(new_size)

    • new_size:新内存块所需的字节数。

  • 返回值

    • 成功:返回指向新内存块首地址的指针(可能与原地址相同也可能不同)。

    • 失败:返回 NULL,且原内存块保持不变(这是关键保证)。

  • 注意:返回的指针需要使用者自行显式转换为目标类型(在 C 中可选,但推荐)。

②函数行为

realloc 负责将 ptr 所指向的已分配内存块的大小调整为 new_size 字节,同时尽可能保留原有数据。具体行为可分为以下几种情况:

  • 扩展内存(new_size > 原大小

    • 如果当前内存块后面有足够的空闲空间,realloc 会直接扩展该块,原地址不变,新增的字节未初始化(内容不确定)。

    • 如果后方空间不足,realloc 会执行以下步骤:

      1. 分配一个大小为 new_size新内存块

      2. 将原内存块中的全部数据逐字节拷贝到新块(拷贝大小为原大小,而非新大小)。

      3. 释放原内存块。

      4. 返回新块的地址。

    • 扩展后多出的字节区域没有初始化 ,与 malloc 相同。

  • 缩小内存(new_size < 原大小

    • realloc 可能直接缩小原块并返回原指针(尾部分割回堆),也可能重新分配并拷贝(某些实现或碎片策略决定)。

    • 无论哪种实现,截断部分的数据将丢失,但剩余部分的数据保持原样。

    • 通常缩容操作不会失败 (除非 new_size 为 0 的特殊情况)。

  • new_size == 0ptr != NULL

    • ​​​​

    • 可能等同于 free(ptr) 并返回 NULL

    • 也可能返回一个唯一指针(不可解引用),此时仍需调用 free 释放。

    • 可移植代码应避免依赖这种行为 ,要么显式调用 free(ptr) 后将 ptrNULL,要么保证不传入 0 大小。

  • ptr == NULL

③使用示例

复制代码
#include <stdio.h>
#include <stdlib.h>

int main() 
{
    int capacity = 5;
    int count = 0;
    int *arr = malloc(capacity * sizeof(int));
    if (!arr) return 1;

    int input;
    while (scanf("%d", &input) == 1) 
    {
        if (count == capacity) 
        {
            capacity *= 2;
            int *tmp = realloc(arr, capacity * sizeof(int));
            if (!tmp) 
            {
                printf("内存不足\n");
                free(arr);
                return 1;
            }
            arr = tmp;
        }
        arr[count++] = input;
    }

    // 使用 arr ...
    free(arr);
    return 0;
}

④使用细节

  • 使用前必须检查返回值

  • 不要弄丢原始指针指向的位置

  • 空间使用完毕之后及时 free ,防止内存泄漏

  • 不允许有任何指针指向原空间下的地址(新空间创建后指向原来空间中的指针会变成野指针)

    复制代码
    int *arr = malloc(10 * sizeof(int));
    int *middle = &arr[5];
    int *tmp = realloc(arr, 20 * sizeof(int));
    if (tmp) arr = tmp;
    // middle 现在可能无效!除非能确保 arr 没变。
  • 先使用临时指针保存新的空间地址

    复制代码
    // 危险:直接使用
    int *arr = malloc(10 * sizeof(int));
    arr = realloc(arr, 20 * sizeof(int));   // 若失败,arr 变为 NULL,原内存丢失
    
    // 正确做法:空间开辟成功后再使用
    int *tmp = realloc(arr, 20 * sizeof(int));
    if (tmp == NULL) 
    {
        // 处理失败:arr 仍然有效,可继续使用或释放
        perror("realloc failed");
        free(arr);
        exit(EXIT_FAILURE);
    }
    arr = tmp;  // 确认成功后再赋值

二、内存空间释放

1.free

①函数原型

复制代码
#include <stdlib.h>
void free(void *ptr);
  • 参数ptr ------ 指向先前由 malloccallocrealloc 返回的内存块的指针。也可以是空指针(NULL)。

  • 返回值:无。

  • 语义 :释放 ptr 指向的动态内存,将其归还给堆管理器,以便后续重新分配。

②函数行为

  1. 内存所有权交还:该内存块不再属于程序,程序不得再通过任何指针访问它(读或写)。

  2. 对象生命周期结束 :存储在块内的任何对象(如结构体、数组)从逻辑上被销毁。如果这些对象包含指向其他资源的指针,必须在 free 前手动清理(因为 free 不会递归释放它们)。

  3. 不改变指针本身的值free 仅处理 ptr 指向的内存块,并不修改 ptr 变量本身。调用后,ptr 仍然保存着原来的地址,但该地址已无效(悬挂指针)。

  4. NULL 安全无操作 :如果 ptr == NULLfree 什么也不做。这是标准明确保证的。

③使用示例

复制代码
#include <stdlib.h>

int main()
{
    int *p = malloc(sizeof(int) * 10);
    if (p) 
    {
        // 使用 p ...
        free(p);   // 不再需要,立即释放
        p = NULL;  // 可选但强烈推荐
    }
    return 0;
}

④使用细节

  • 不能释放非动态分配的内存:只能 free 来自 malloc/calloc/realloc 的指针,以下均是未定义行为

    • 栈变量地址:int x; free(&x);

    • 全局/静态变量地址

    • 字符串字面量:char *s = "hello"; free(s);

    • 已经释放过的指针(双重释放)

    • 通过指针运算偏移后的地址(如 ptr+1),哪怕原指针合法

  • 不能重复释放同一块堆区地址

    复制代码
    int *p = malloc(10);
    free(p);
    free(p);   // 未定义行为!可能导致堆损坏或安全漏洞。
  • 释放后不能再次使用

    复制代码
    int *p = malloc(sizeof(int));
    *p = 42;
    free(p);
    printf("%d\n", *p);   // 未定义行为,p 已是悬挂指针
  • 空间释放后及时将指针置为 NULL

相关推荐
vibecoding日记19 小时前
双非如何快速入职字节等大厂大模型?真实案例分析:推理优化和投机解码
算法·求职·大模型工程师
yszaygr213821 小时前
Verilog参数化游程编码RLE模块
算法
望易21 小时前
刚设计的大模型架构-双域耦合认知框架
算法·架构
复杂网络1 天前
多个 Claude Code 与多个 Codex 协同工作:设计与实现方案
算法
apocelipes2 天前
常用编程语言和库的正则表达式性能对比
c语言·c++·python·性能优化·golang·开发工具和环境
HjhIron2 天前
面试常客:字符串算法从入门到进阶
算法·面试
吴佳浩2 天前
DeepSeek DSpark:Confidence-Scheduled Speculative Decoding 技术解析
人工智能·算法·deepseek
触底反弹2 天前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法
vivo互联网技术2 天前
ICLR 2026 | 基于后验采样的图像恢复方法LearnIR:人脸去阴影、去雾
人工智能·算法·aigc