
🏠个人主页:黎雁
🎬作者简介:C/C++/JAVA后端开发学习者
❄️个人专栏:C语言、数据结构(C语言)、EasyX、游戏、规划
✨ 从来绝巘须孤往,万里同尘即玉京

文章目录
- [C 语言动态内存管理进阶:常见错误排查 + 经典笔试题深度解析🚨](#C 语言动态内存管理进阶:常见错误排查 + 经典笔试题深度解析🚨)
-
- [前景回顾:动态内存核心速记 📝](#前景回顾:动态内存核心速记 📝)
- [一、动态内存的6大常见错误(避坑指南) 🚫](#一、动态内存的6大常见错误(避坑指南) 🚫)
- [二、动态内存经典笔试题分析(笔试高频) 📝](#二、动态内存经典笔试题分析(笔试高频) 📝)
- [写在最后 📝](#写在最后 📝)
C 语言动态内存管理进阶:常见错误排查 + 经典笔试题深度解析🚨
在上一篇中,我们掌握了 malloc、calloc、realloc、free 四大函数的基础用法,但动态内存管理是C语言的高频"踩坑区"!这一篇我们聚焦常见动态内存错误 和经典笔试题,从错误原因、代码示例到修复方案,全方位帮你避坑,同时吃透笔试面试中的核心考点!
前景回顾:动态内存核心速记 📝
回顾上一篇的核心知识点,能帮我们更好理解错误产生的根源:
malloc申请内存后必须检查返回值 ,避免解引用NULL指针。realloc调整内存时,必须用新指针接收返回值,防止丢失原有内存。free释放内存后必须将指针置空,杜绝野指针问题。- 动态内存遵循"申请-释放"成对原则,否则会导致内存泄漏。
一、动态内存的6大常见错误(避坑指南) 🚫
动态内存错误是新手最容易犯的问题,我们逐个拆解错误场景、原因和修复方案:
错误1:对空指针的解引用操作
错误代码
c
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(40);
// 未检查p是否为NULL!
for (int i = 0; i < 10; i++)
{
*(p + i) = i + 1; // 若p为NULL,解引用会导致程序崩溃
}
free(p);
p = NULL;
return 0;
}
错误原因
malloc 可能申请失败返回 NULL,直接解引用 NULL 指针是非法操作,会触发程序崩溃。
修复方案
申请内存后必须先检查返回值:
c
int* p = (int*)malloc(40);
if (p == NULL) // 关键检查
{
perror("malloc");
return 1;
}
错误2:对动态开辟空间的越界访问
错误场景
c
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL) { perror("malloc"); return 1; }
// 申请了10个int空间,却访问第11个元素(越界)
for (int i = 0; i <= 10; i++)
{
p[i] = i + 1; // i=10时越界访问
}
错误原因
动态开辟的内存和数组一样有大小限制,越界访问会破坏内存中的其他数据,导致程序行为异常(崩溃、数据错乱等)。
修复方案
严格控制访问范围,确保不超过申请的内存大小。
错误3:对非动态开辟内存使用 free 释放
错误代码
c
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int* p = &a; // p指向栈区的局部变量(非动态内存)
*p = 100;
free(p); // 错误:free只能释放堆区动态内存
p = NULL;
return 0;
}
错误原因
free 仅用于释放 malloc/calloc/realloc 申请的堆区内存,释放栈区/静态区内存会导致未定义行为(程序崩溃)。
修复方案
只对动态开辟的内存使用 free,栈区变量由系统自动释放,无需手动处理。
错误4:使用 free 释放动态内存的一部分
错误代码
c
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL) { perror("malloc"); return 1; }
// p指向内存起始地址 → 循环后指向中间位置
for (int i = 0; i < 5; i++)
{
*p = 5;
p++; // 指针偏移,不再指向起始地址
}
free(p); // 错误:p不是动态内存的起始地址
p = NULL;
return 0;
}
错误原因
free 的参数必须是动态内存的起始地址,释放中间地址会导致内存释放失败,甚至程序崩溃。
修复方案
保留动态内存的起始地址,不要修改原指针的指向(可以用临时指针遍历):
c
int* temp = p; // 保留起始地址
for (int i = 0; i < 5; i++)
{
*temp = 5;
temp++; // 用临时指针偏移
}
free(p); // 释放起始地址
p = NULL;
错误5:对同一块动态内存多次释放
错误代码
c
#include <stdio.h>
#include <stdlib.h>
void test()
{
int* p = (int*)malloc(100);
if (p == NULL) { perror("malloc"); return; }
free(p);
// p未置空,后续free会重复释放
free(p); // 错误:重复释放同一块内存
}
int main()
{
test();
return 0;
}
错误原因
重复释放同一块动态内存会破坏内存管理机制,导致程序崩溃。
修复方案
free 后立即将指针置空 ,free(NULL) 是安全操作:
c
free(p);
p = NULL; // 关键:置空指针
free(p); // 安全,free(NULL) 什么都不做
错误6:动态开辟内存忘记释放(内存泄漏)
错误代码
c
#include <stdio.h>
#include <stdlib.h>
void test()
{
int* p = (int*)malloc(100);
if (p == NULL) { perror("malloc"); return; }
*p = 20;
// 未释放p指向的内存,函数结束后p销毁,内存无法释放
}
int main()
{
while (1)
{
test(); // 持续申请内存,不释放 → 内存泄漏
}
return 0;
}
错误原因
内存泄漏是指动态申请的内存不再使用,但未释放,导致系统可用内存逐渐减少。短期程序影响不大,但长期运行的程序(如服务器)会因内存耗尽崩溃。
修复方案
遵循"申请-释放"成对原则 ,使用完动态内存后必须 free,并置空指针:
c
void test()
{
int* p = (int*)malloc(100);
if (p == NULL) { perror("malloc"); return; }
*p = 20;
free(p); // 释放内存
p = NULL; // 置空指针
}
常见错误总结表
| 错误类型 | 核心原因 | 修复关键 |
|---|---|---|
| 解引用NULL指针 | 未检查malloc返回值 | 申请后必判空 |
| 越界访问 | 访问范围超过申请大小 | 严格控制访问边界 |
| 释放非动态内存 | free误用在栈区/静态区 | 仅释放堆区动态内存 |
| 释放内存一部分 | 原指针偏移,丢失起始地址 | 保留起始地址,用临时指针遍历 |
| 重复释放 | free后未置空指针 | free后立即置空 |
| 内存泄漏 | 申请后未释放 | 申请-释放成对出现 |
二、动态内存经典笔试题分析(笔试高频) 📝
我们拆解3道经典笔试题,从错误分析到代码修复,帮你吃透核心考点。
笔试题1:传值调用导致的NULL解引用+内存泄漏
错误代码
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void GetMemory(char* p)
{
p = (char*)malloc(100); // 未检查返回值
}
void Test(void)
{
char* str = NULL;
GetMemory(str); // 传值调用,str未被修改
strcpy(str, "hello world"); // 错误:解引用NULL指针
printf(str);
}
int main()
{
Test();
return 0;
}
错误分析
- 传值调用问题 :
GetMemory的参数p是str的拷贝,修改p不会改变原指针str,str仍为NULL。 - 解引用NULL :
strcpy对NULL指针解引用,程序崩溃。 - 内存泄漏 :
GetMemory中malloc申请的内存无法释放(指针p销毁)。
修复方案1:传址调用
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void GetMemory(char** p) // 二级指针接收地址
{
*p = (char*)malloc(100);
if (*p == NULL) // 检查返回值
{
perror("malloc");
return;
}
}
void Test(void)
{
char* str = NULL;
GetMemory(&str); // 传str的地址
strcpy(str, "hello world");
printf(str);
free(str); // 释放内存
str = NULL; // 置空
}
int main()
{
Test();
return 0;
}
修复方案2:返回堆区指针
c
char* GetMemory()
{
char* p = (char*)malloc(100);
if (p == NULL) { perror("malloc"); return NULL; }
return p; // 返回堆区指针
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
if (str != NULL) // 判空
{
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
}
笔试题2:返回栈区空间地址(野指针)
错误代码
c
#include <stdio.h>
char* GetMemory(void)
{
char p[] = "hello world"; // p是栈区局部变量
return p; // 返回栈区地址,函数结束后p销毁
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str); // str是野指针,输出乱码
}
int main()
{
Test();
return 0;
}
错误分析
- 栈区变量
p在函数GetMemory执行完后会被系统自动释放,返回的地址变成无效地址,str成为野指针,printf输出随机乱码(如"烫烫烫")。
核心区别:栈区地址 vs 堆区地址
| 地址类型 | 生命周期 | 释放方式 | 返回后是否可用 |
|---|---|---|---|
| 栈区地址 | 函数执行期间 | 系统自动释放 | 不可用(野指针) |
| 堆区地址 | 程序运行期间 | 手动free释放 | 可用(需手动释放) |
修复方案
将栈区数组改为堆区动态内存:
c
char* GetMemory(void)
{
char* p = (char*)malloc(12); // 堆区内存
if (p == NULL) { perror("malloc"); return NULL; }
strcpy(p, "hello world");
return p; // 返回堆区地址
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
if (str != NULL)
{
printf(str);
free(str);
str = NULL;
}
}
笔试题3:内存泄漏(未释放动态内存)
错误代码
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
// 未释放str指向的内存 → 内存泄漏
}
int main()
{
Test();
return 0;
}
错误分析
GetMemory 中 malloc 申请的内存未被 free,函数结束后 str 销毁,内存无法释放,导致内存泄漏。
修复方案
使用完动态内存后及时释放:
c
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
if (str != NULL)
{
strcpy(str, "hello");
printf(str);
free(str); // 释放内存
str = NULL; // 置空
}
}
写在最后 📝
动态内存的错误大多源于对"内存生命周期"和"指针指向"的理解不足,掌握这些核心要点能帮你避坑:
- 判空是底线 :
malloc/calloc/realloc返回值必须检查是否为NULL。 - 指针别乱改:保留动态内存的起始地址,避免偏移后释放失败。
- 释放要彻底 :
free后必须置空指针,杜绝野指针和重复释放。 - 内存不泄漏:申请和释放成对出现,栈区地址别返回。
下一篇我们将讲解动态内存的进阶知识点------柔性数组,以及C/C++程序的内存区域划分,帮你构建完整的内存知识体系!
我可以帮你整理动态内存笔试题错误速查手册,把常见错误和修复方案汇总成一页纸,方便你笔试前快速复习,需要吗?