C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C语言动态内存管理

C语言动态内存管理示例详解

作者:Yue丶越

在C语言编程中,动态内存管理是一项核心技能,它允许程序在运行时灵活地分配和释放内存,这篇文章主要介绍了C语言动态内存管理的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

在C语言编程中,静态内存分配(如数组、局部变量)受限于编译时固定大小的特性,无法满足程序运行中动态调整内存的需求。动态内存管理则可通过malloccalloc等函数,可以让我们自主申请和释放内存,以成为了灵活处理内存需求的核心技术。本文我们将从基础原理出发,结合实战案例,以带你掌握动态内存管理的关键知识点。

一、为什么需要动态内存分配?

静态内存分配(如int arr[10] = {0})存在两个核心局限:

而动态内存分配允许程序在运行时根据需求申请内存,用完后手动释放,极大提升了内存使用的灵活性。例如处理用户输入的数组长度、动态存储不确定数量的数据时,动态内存是我们唯一的选择。

二、动态内存核心函数

C语言提供了4个核心动态内存函数,它们均声明在stdlib.h头文件中,各自适用场景不同,我们在运用时需精准区分。

2.1 malloc

示例代码

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

int main() {
    int num = 0;
    scanf("%d", &num);
    // 申请num个int大小的内存
    int* ptr = (int*)malloc(num * sizeof(int));
    if (NULL != ptr) { // 检查申请是否成功
        for (int i = 0; i < num; i++) {
            *(ptr + i) = 0; // 初始化内存
        }
    }
    free(ptr); // 释放内存
    ptr = NULL; // 避免野指针
    return 0;
}

2.2 free

2.3 calloc

示例代码

int* p = (int*)calloc(10, sizeof(int)); 
if (NULL != p) {
    for (int i = 0; i < 10; i++) {
        printf("%d ", *(p + i)); // 输出:0 0 0 0 0 0 0 0 0 0
    }
}
free(p);
p = NULL;

2.4 realloc

正确示例代码

int* ptr = (int*)malloc(100);
if (NULL != ptr) {
    // 业务处理
}
// 扩容:先存临时指针
int* tmp = (int*)realloc(ptr, 1000); 
if (NULL != tmp) {
    ptr = tmp; // 扩容成功,更新原指针
    // 后续业务处理
}
free(ptr);
ptr = NULL;

三、6个高频动态内存错误

动态内存错误是C语言调试的重灾区,以下6类错误需重点规避,几乎覆盖了我们所有笔试/面试考点。

错误类型错误代码示例后果
对NULL指针解引用int* p = (int*)malloc(INT_MAX/4); *p = 20;malloc失败返回NULL,解引用会导致程序崩溃
越界访问int* p = (int*)malloc(10*sizeof(int)); for(i=0; i<=10; i++) *(p+i)=i;访问超出申请的内存区域,破坏堆区数据,导致程序异常
释放非动态内存int a=10; int* p=&a; free(p);释放栈区内存,触发未定义行为(程序崩溃或乱码)
释放部分动态内存int* p = (int*)malloc(100); p++; free(p);p不再指向内存起始地址,free无法识别,导致内存泄漏
重复释放int* p = (int*)malloc(100); free(p); free(p);同一内存被多次释放,破坏堆区结构,程序崩溃
内存泄漏void test(){int* p=(int*)malloc(100);}申请的内存未释放,程序运行中内存持续占用,最终耗尽

四、动态内存经典笔试题

以下4道笔试题是企业面试高频题,需结合内存原理分析错误根源。

题目1:指针传值导致内存泄漏

void GetMemory(char *p) { p = (char *)malloc(100); }
void Test(void) {
    char *str = NULL;
    GetMemory(str); // 传值调用,p是str的副本
    strcpy(str, "hello world"); // str仍为NULL,解引用崩溃
    printf(str);
}

错误原因GetMemory采用值传递,pstr的副本,malloc申请的内存地址仅存于p,未传递给strstr始终为NULL,strcpy时解引用崩溃,且malloc的内存未释放,造成泄漏。

修正方案:改用指针的指针(char** p)传址调用,将内存地址赋值给*p

题目2:栈区内存释放后访问

char *GetMemory(void) {
    char p[] = "hello world"; // 栈区局部数组
    return p; // 返回栈区地址,函数结束后p被释放
}
void Test(void) {
    char *str = NULL;
    str = GetMemory(); // str指向已释放的栈区内存
    printf(str); // 访问“野内存”,输出乱码
}

错误原因p是栈区局部数组,函数GetMemory结束后,栈区内存被系统回收。str指向的地址已无效,访问时属于“野内存”操作,结果不确定。

修正方案:将p改为动态内存(char* p = (char*)malloc(12);),或用静态数组(static char p[])。

题目3:正确传址但未释放内存

void GetMemory(char **p, int num) { *p = (char *)malloc(num); }
void Test(void) {
    char *str = NULL;
    GetMemory(&str, 100); // 传址调用,str获得内存地址
    strcpy(str, "hello");
    printf(str); // 输出“hello”
}

潜在问题malloc申请的内存未用free释放,程序结束前会造成内存泄漏。

修正方案:在printf后添加free(str); str = NULL;

题目4:释放后仍访问野指针

void Test(void) {
    char *str = (char *)malloc(100);
    strcpy(str, "hello");
    free(str); // 释放内存,但str未置NULL
    if (str != NULL) { // 条件为真,错误访问
        strcpy(str, "world"); // 写入已释放的内存,破坏堆区
        printf(str);
    }
}

错误原因free(str)后,内存被归还给系统,但str仍指向原地址(野指针)。if条件误判为“非空”,strcpy向已释放的内存写入数据,破坏堆区结构,可能导致程序崩溃。

修正方案free(str)后立即将str置为NULL(str = NULL;)。

五、柔性数组

C99标准允许结构体的最后一个成员为“未知大小的数组”,称为柔性数组,适用于需要“结构体+动态数组”连续内存的场景。

5.1 柔性数组的定义

typedef struct st_type {
    int i; // 前面至少有一个其他成员
    int a[]; // 柔性数组成员(部分编译器支持int a[0];)
} type_a;

5.2 柔性数组的使用示例

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

typedef struct st_type {
    int i;
    int a[]; // 柔性数组
} type_a;

int main() {
    // 申请“结构体大小 + 100个int”的连续内存
    type_a *p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
    if (NULL != p) {
        p->i = 100; // 初始化非柔性成员
        for (int i = 0; i < 100; i++) {
            p->a[i] = i; // 操作柔性数组
        }
    }
    free(p); // 一次释放所有内存,包括柔性数组
    p = NULL;
    return 0;
}

5.3 柔性数组的优势

若用“结构体+指针”(如下代码)实现类似功能,柔性数组有两大核心优势:

// 对比方案:结构体+指针
typedef struct st_type {
    int i;
    int *p_a; // 指针指向动态内存
} type_a;

六、C/C++程序内存区域划分:从内核到栈区

理解内存区域是掌握动态内存的基础,C/C++程序的内存空间从高到低分为5个区域:

内存区域存储内容生命周期管理方式
内核空间操作系统内核代码/数据系统运行期间操作系统管理
栈区(向下增长)局部变量、函数参数、返回值函数执行期间编译器自动分配/释放
内存映射段动态库、文件映射随进程/库加载/卸载系统管理
堆区(向上增长)动态内存(malloc/calloc等)程序员分配/释放(或程序结束后OS回收)程序员手动管理
数据段(静态区)全局变量、静态变量(static程序运行期间程序结束后系统释放
代码段函数二进制代码、只读常量(如字符串字面量)程序运行期间只读,系统管理

关键区分:堆区与栈区的差异是高频考点——栈区内存自动管理,大小有限(通常几MB)。堆区内存手动管理,大小可至GB级,是动态内存的核心区域。

掌握动态内存管理,不仅能解决实际开发中的灵活内存需求,更能深入理解C语言的内存模型,为后续底层开发(如操作系统、嵌入式)打下坚实基础。

至此,我们已梳理完“动态内存管理”的全部内容了。最后我们在文末来进行一个投个票,告诉我你对哪部分内容最感兴趣、收获最大,也欢迎在评论区聊聊你的学习感受。

总结

到此这篇关于C语言动态内存管理的文章就介绍到这了,更多相关C语言动态内存管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文