【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

相关推荐
眠りたいです1 小时前
现代C++:C++14中的新语言特性和库特性
c语言·开发语言·c++
Black蜡笔小新1 小时前
自动化AI算法训练服务器DLTM助力医学影像分析进入AI智能分析新时代
人工智能·算法·自动化
手写码匠2 小时前
深入解析大模型架构之争:全能通用模型 vs 领域专精模型
人工智能·深度学习·算法·aigc
浅念-2 小时前
LeetCode 回溯算法题——综合练习
数据结构·c++·算法·leetcode·职场和发展·深度优先·dfs
列星随旋3 小时前
线段树和树状数组的学习
学习·算法
ytttr8734 小时前
OPC UA 协议栈 C 语言实现
c语言·开发语言·mfc
song5014 小时前
Ascend C 算子开发:从入门到上手
c语言·开发语言·图像处理·人工智能·分布式·flutter·交互
小a杰.5 小时前
Ascend C编程语言进阶:高性能算子开发技巧
android·c语言·开发语言
全糖可乐气泡水5 小时前
Codex适配国产信创环境安装部署与技术适配全解析
开发语言·git·python·算法·百度