点赞+关注私信领取全部代码资料~
一、万能指针(void*)—— 通用地址存储工具
万能指针是 C 语言中无类型指针,核心作用是存储任意类型的内存地址,是跨类型地址传递的通用解决方案,也是标准库函数的常用参数类型。
1.1 万能指针的定义与核心特性
void *p; // 万能指针标准定义
核心特性 1:可存储任意数据类型的地址
万能指针无类型限制,能接收 int、char、float、数组、结构体等所有类型的变量 / 数据地址,打破普通指针的类型绑定限制。
int a = 10;
char ch = 'a';
float f = 3.14;
void *p1 = &a; // 合法:存储int类型地址
void *p2 = &ch; // 合法:存储char类型地址
void *p3 = &f; // 合法:存储float类型地址
核心特性 2:无任何运算能力(新手必记)
普通指针的运算能力由其类型决定(如 int*+1 跳 4 字节),但万能指针void*是无类型指针,编译器无法确定其指向的数据类型,因此不支持任何指针运算(+、-、++、-- 均非法)。
void *p = &a;
// p++; // 非法:万能指针无运算能力,编译器直接报错
// p+2; // 非法:无类型,无法计算偏移字节数
关键使用原则
万能指针使用前必须强制类型转换为对应的数据类型指针,才能进行解引用或指针运算,这是万能指针的核心使用规范。
int a = 10;
void *p = &a;
// 正确用法:先强制转换为int*,再解引用
printf("%d\n", *(int *)p); // 输出10
// 正确用法:转换后进行指针运算
int *q = (int *)p;
q++; // 合法:int*类型有运算能力
1.2 NULL 宏 —— 指针的标准初始化值
NULL 是 C 语言预定义的宏,是指针初始化的标准默认值,用于表示指针 “未指向任何有效内存地址”,避免野指针产生。
1.2.1 NULL 的本质定义
#define NULL ((void *)0)
NULL 的本质是强制转换为 void * 的 0 地址,0 地址是系统保留地址,用户程序无权限访问,因此用 NULL 初始化的指针是 “安全的空指针”。
1.2.2 NULL 的核心应用:指针初始化
所有指针在定义时,若暂时无法指向有效地址,必须初始化为 NULL,这是 C 语言编程的通用规范,也是避免野指针的首要手段。
// 正确写法:指针定义即初始化,要么指向有效地址,要么置NULL
int *p1 = NULL;
void *p2 = NULL;
int **p3 = NULL;
// 错误写法:未初始化,成为野指针
int *p4;
1.2.3 空指针的使用注意
NULL 指针不可直接解引用(0 地址无访问权限),解引用前必须判空检查,这是指针操作的必备防御性代码。
int *p = NULL;
if (p != NULL) { // 判空检查,避免空指针解引用
*p = 10;
}
二、野指针 ——C 语言的 “隐形炸弹”(必避坑)
野指针是 C 语言中最危险的错误之一,也是校招面试高频考点,其本质是指向 “无效 / 无权限内存地址” 的指针,程序使用野指针会导致内存访问错误、程序崩溃、数据乱码等不可控问题。
2.1 野指针的本质定义
野指针是存储随机内存地址的指针,该地址要么是系统保留地址(无访问权限)、要么是已释放的内存地址、要么是超出程序寻址范围的地址,程序对野指针的任何操作(解引用、运算)都属于未定义行为。
2.2 野指针的 3 大产生原因(全覆盖)
原因 1:指针未初始化(最常见,新手必犯)
指针定义时未赋值,编译器会为其分配一个随机的垃圾值(随机内存地址),直接成为野指针。
int *p; // 未初始化,p为野指针,存储随机地址
*p = 10; // 致命错误:解引用野指针,程序直接崩溃
原因 2:指针指向的内存被释放后未置 NULL
堆内存通过 free () 释放后,原指针仍指向该内存地址(此时该地址已被系统回收,无访问权限),成为野指针。
int *p = (int *)malloc(4);
free(p); // 释放堆内存,p仍指向原地址,成为野指针
*p = 10; // 致命错误:解引用已释放的野指针
原因 3:指针越界访问
指针运算超出了合法的内存范围,指向了程序无权限的内存地址,成为野指针。
int arr[3] = {1,2,3};
int *p = arr;
p += 5; // 指针越界,指向数组外的随机地址,成为野指针
*p = 10; // 致命错误:解引用越界野指针
2.3 野指针的 4 大规避方法(万能解决方案)
野指针无法被编译器检测,只能通过编程规范避免,以下 4 种方法可全覆盖规避野指针问题:
- 指针定义即初始化:要么指向有效地址,要么置 NULL(
int *p = NULL;); - 内存释放后立即置 NULL:
free(p); p = NULL;,让指针成为可检测的空指针; - 解引用前必判空:所有指针操作前加
if (p != NULL)检查; - 严格限制指针运算范围:遍历数组 / 字符串时,以长度或
'\0'作为结束条件,避免越界。
三、二级指针(int**)—— 指针的指针(进阶核心)
二级指针是存储一级指针地址的指针,是指针进阶的核心内容,主要用于修改一级指针的指向(如堆内存动态扩容)、二维数组操作、** 命令行参数(argv)** 等场景,理解二级指针是掌握 C 语言内存操作的关键。
3.1 二级指针的核心原理
核心原则(继承一级指针)
- 指针的本质永远是存储内存地址,与普通指针无区别;
- 指针的类型由其存储的地址类型决定 —— 一级指针存储普通变量的地址,二级指针存储一级指针的地址。
通俗理解
- 普通变量(int a):存储数据值,地址为
&a; - 一级指针(int *p):存储普通变量的地址(
p=&a),自身的地址为&p; - 二级指针(int q):存储一级指针的地址 **(
q=&p),是 “指向指针的指针”。
3.2 二级指针的定义与初始化(标准写法)
标准格式
// 一级指针:存储int变量的地址
int a = 10;
int *p = &a;
// 二级指针:存储int*类型一级指针的地址
int **q = &p;
关键说明
q是二级指针变量,简称二级指针;q的类型是int**,表示 “指向 int * 类型指针的指针”,限制其只能存储int*类型一级指针的地址;- 二级指针的解引用分两层:
*q表示取 q 指向的内容(即一级指针 p 的值,&a),**q表示取最终指向的普通变量值(即 a 的值,10)。
printf("*q = %p\n", *q); // 输出p的值,即&a的地址
printf("**q = %d\n", **q); // 输出a的值,10
3.3 二级指针的内存大小与运算能力
二级指针本质仍是指针,其内存大小和运算能力遵循指针的通用规则,与 “几级指针” 无关,仅由去掉一个 * 后的类型决定。
特性 1:二级指针的内存大小 —— 与一级指针一致
64 位系统中,所有指针的内存大小均为 8 字节(32 位系统为 4 字节),无论一级指针(int*)、二级指针(int**)、万能指针(void*),占用的存储空间完全相同。
int **q = NULL;
printf("sizeof(q) = %zd\n", sizeof(q)); // 64位系统输出8
特性 2:二级指针的运算能力计算公式
二级指针运算能力 = sizeof (去掉一个)**,即去掉二级指针类型中最外层的一个,计算剩余类型的大小。
int **q;
// 运算能力 = sizeof(int*) = 8字节(64位系统)
q++; // 合法:q向后移动8字节,指向相邻的int*类型指针地址
指针运算能力通用表(一级 / 二级对比)
| 指针类型 | 去掉一个 * 后的类型 | 内存大小(64 位) | 运算能力(64 位) | 计算依据 |
|---|---|---|---|---|
| int*(一级) | int | 8 字节 | 4 字节 | sizeof(int)=4 |
| int**(二级) | int* | 8 字节 | 8 字节 | sizeof(int*)=8 |
| char**(二级) | char* | 8 字节 | 1 字节 | sizeof(char*)=8?注意:char** 运算能力 = sizeof (char*)=8,char * 运算能力 = sizeof (char)=1 |
四、堆内存动态开辟 ——C 语言的内存管理核心
C 语言的内存分为栈区、堆区、全局 / 静态区、常量区,其中堆区是唯一由用户程序主动控制生命周期的内存区域,通过动态开辟函数申请,通过 free () 释放,是实现动态数组、链表、树等动态数据结构的基础。
4.1 堆区的核心特性(与栈区对比)
堆区与栈区是程序运行时最常用的两个内存区域,核心差异体现在生命周期、控制权、大小上,对比理解更清晰:
| 特性 | 堆区(动态内存) | 栈区(局部变量) |
|---|---|---|
| 生命周期 | 随用户申请而产生,随free () 释放而销毁,程序不释放则一直占用(直到程序退出) | 随函数调用而创建,随函数返回而自动销毁,生命周期由编译器控制 |
| 控制权 | 完全由用户程序控制,可灵活申请、扩容、释放 | 由编译器自动管理,用户无法手动控制 |
| 内存大小 | 空间大(通常为 GB 级),适合存储大量数据(如大数组、结构体链表) | 空间小(通常为 MB 级),适合存储少量局部变量 |
| 申请方式 | 调用标准库函数(malloc/calloc/realloc)主动申请 | 定义局部变量时编译器自动分配 |
| 初始化 | 申请后默认是随机垃圾值(malloc/realloc),calloc 会自动初始化为 0 | 局部变量默认是随机垃圾值,静态局部变量初始化为 0 |
4.2 堆内存的开辟函数(3 个核心)
堆内存开辟的 3 个函数均定义在<stdlib.h>头文件中,使用前必须包含该头文件,函数返回值均为void*(万能指针),需强制转换为对应类型后使用。
4.2.1 malloc (3)—— 基础堆内存开辟
函数原型
void *malloc(size_t size);
功能说明
申请size 字节的连续堆内存,返回指向该内存起始地址的万能指针;申请失败返回NULL(如内存不足)。
关键特性
- 申请的内存未初始化,存储的是随机垃圾值;
- size 是
size_t类型(无符号整数),通常用sizeof()计算,避免手动写死字节数(跨平台兼容)。
标准使用写法(含判空)
#include <stdlib.h>
#include <stdio.h>
int main() {
// 申请4字节堆内存,存储int类型数据,强制转换为int*
int *p = (int *)malloc(sizeof(int));
// 申请失败判空,必备防御性代码
if (p == NULL) {
perror("malloc failed"); // 打印错误信息
return -1;
}
*p = 10; // 对堆内存赋值
printf("*p = %d\n", *p); // 输出10
free(p); // 释放堆内存
p = NULL; // 释放后置NULL,避免野指针
return 0;
}
4.2.2 calloc (3)—— 带初始化的堆内存开辟
函数原型
void *calloc(size_t nmemb, size_t size);
功能说明
申请nmemb 个 size 字节的连续堆内存(总大小 = nmemb*size),自动将所有字节初始化为 0,返回万能指针;失败返回 NULL。
核心优势
无需手动初始化,适合开辟数组(直接初始化为 0,避免垃圾值),比 malloc 更适合动态数组的初始开辟。
标准使用写法
// 开辟5个int类型的动态数组,总大小=5*4=20字节,自动初始化为0
int *arr = (int *)calloc(5, sizeof(int));
if (arr == NULL) {
perror("calloc failed");
return -1;
}
// 数组元素均为0
for (int i=0; i<5; i++) {
printf("%d ", arr[i]); // 输出0 0 0 0 0
}
free(arr);
arr = NULL;
4.2.3 realloc (3)—— 堆内存的扩容 / 缩容
函数原型
void *realloc(void *ptr, size_t size);
功能说明
修改已开辟的堆内存ptr的大小为size字节,实现扩容或缩容,返回新的内存起始地址;失败返回 NULL。
核心特性(重点!)
ptr必须是malloc/calloc/realloc返回的堆内存地址,不可是栈内存地址;- 扩容时,若原内存后有连续空闲空间,直接在原地址后扩容(原地扩容);若无,则重新申请一块新的 size 字节内存,将原数据拷贝到新内存,释放原内存(异地扩容)——因此 realloc 返回的新地址可能与原地址不同;
- 若
ptr=NULL,则 realloc 等价于 malloc(realloc(NULL, size) = malloc(size)); - 若
size=0,则 realloc 等价于 free(realloc(ptr, 0) = free(ptr))。
标准使用写法(扩容动态数组)
// 先开辟5个int的动态数组
int *arr = (int *)calloc(5, sizeof(int));
if (arr == NULL) { perror("calloc failed"); return -1; }
// 扩容为10个int的数组,总大小=10*4=40字节
int *new_arr = (int *)realloc(arr, 10*sizeof(int));
if (new_arr == NULL) { // 扩容失败,原内存仍有效,需避免内存泄漏
perror("realloc failed");
free(arr);
arr = NULL;
return -1;
}
arr = new_arr; // 指向新的扩容后地址
// 使用后释放
free(arr);
arr = NULL;
扩容关键注意
realloc 扩容失败时,原内存 ptr 不会被释放,因此不能直接写arr = realloc(arr, size),否则扩容失败会导致原地址丢失,造成内存泄漏。
4.3 堆内存的释放函数 ——free (3)
堆内存不会自动销毁,用户必须主动调用 free () 释放,否则会造成内存泄漏(程序运行时占用的内存越来越多,最终导致内存耗尽)。
函数原型
void free(void *ptr);
功能说明
释放ptr指向的堆内存,将内存归还给系统,供其他程序申请使用。
核心使用规则(必守!)
ptr必须是malloc/calloc/realloc返回的有效堆内存地址,不可释放栈内存、NULL、已释放的内存;- 堆内存释放后必须将指针置 NULL,避免成为野指针;
- 动态开辟的内存必须成对使用(申请一次,释放一次),避免内存泄漏或重复释放;
- free()仅释放内存,不会修改指针的值(指针仍指向原地址)。
错误用法示例(必避)
int a = 10;
int *p1 = &a;
int *p2 = (int *)malloc(4);
free(p1); // 错误:释放栈内存地址
free(p2); // 正确:释放堆内存
free(p2); // 错误:重复释放已释放的内存
// p2未置NULL,成为野指针
*p2 = 20; // 错误:解引用野指针
4.4 堆内存实战 —— 动态数组的增删改查(完整可运行代码)
动态数组是堆内存最经典的应用,通过malloc/calloc 开辟、realloc 扩容、free 释放实现数组大小的灵活调整,结合二级指针实现数组地址的修改,以下实现动态数组的尾插添加、按值删除、遍历打印核心功能,代码包含边界处理、内存扩容、野指针规避,可直接编译运行。
4.4.1 实战需求与宏定义
核心需求
- 尾插添加:向动态数组末尾插入数据,数组容量不足时自动扩容;
- 按值删除:删除数组中第一个匹配指定值的元素,后续元素前移;
- 遍历打印:遍历输出数组所有元素,原数组只读;
宏定义
#define INCREASE 5 // 每次扩容的步长:容量不足时,每次增加5个int位置
头文件包含
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // 用于memmove内存拷贝
4.4.2 函数原型设计(贴合实战)
// 1. 尾插添加元素:成功返回0,失败返回-1
int add_elm(int **arr, int *capacity, int *nmemb, int data);
// 2. 按值删除元素:找到并删除返回0,未找到返回-1,失败返回-2
int del_elm(int **arr, int *capacity, int *nmemb, int key);
// 3. 遍历打印数组:const修饰,原数组只读
void show_elm(const int *arr, int nmemb);
参数说明(核心!二级指针的应用)
int **arr:二级指针,指向动态数组的一级指针,用于修改一级指针的指向(扩容时 realloc 返回新地址,需通过二级指针更新);int *capacity:指向数组容量的指针,容量表示数组当前可存储的最大元素个数,扩容时修改其值;int *nmemb:指向数组已存储元素个数的指针,添加 / 删除时修改其值;data/key:待添加的数据 / 待删除的目标值;const int *arr:常量指针,保证遍历过程中原数组不被修改。
4.4.3 完整实现代码(带详细注释)
1. 尾插添加元素函数 add_elm
int add_elm(int **arr, int *capacity, int *nmemb, int data) {
// 入参合法性检查:避免空指针、容量/个数为负
if (arr == NULL || capacity == NULL || nmemb == NULL
|| *capacity < 0 || *nmemb < 0 || *nmemb > *capacity) {
return -1;
}
// 情况1:数组未开辟(*arr=NULL),首次开辟内存,初始容量为INCREASE
if (*arr == NULL) {
*arr = (int *)calloc(INCREASE, sizeof(int));
if (*arr == NULL) {
perror("calloc failed");
return -1;
}
*capacity = INCREASE; // 初始化容量为5
}
// 情况2:已存储个数 >= 容量,需要扩容
if (*nmemb >= *capacity) {
int new_cap = *capacity + INCREASE; // 新容量=原容量+5
// 扩容:realloc返回新地址,用临时指针接收,避免扩容失败丢失原地址
int *new_arr = (int *)realloc(*arr, new_cap * sizeof(int));
if (new_arr == NULL) {
perror("realloc failed");
return -1;
}
*arr = new_arr; // 更新数组地址为扩容后地址
*capacity = new_cap; // 更新容量为新容量
}
// 尾插数据:将data存入数组末尾(第*nmemb个位置)
(*arr)[*nmemb] = data;
(*nmemb)++; // 已存储个数+1
return 0; // 添加成功
}
2. 按值删除元素函数 del_elm
int del_elm(int **arr, int *capacity, int *nmemb, int key) {
// 入参合法性检查
if (arr == NULL || capacity == NULL || nmemb == NULL
|| *arr == NULL || *capacity <= 0 || *nmemb <= 0) {
return -2;
}
// 遍历数组,查找第一个匹配key的元素下标
int find_idx = -1;
for (int i=0; i<*nmemb; i++) {
if ((*arr)[i] == key) {
find_idx = i;
break; // 找到第一个匹配项,退出循环
}
}
// 未找到匹配元素,返回-1
if (find_idx == -1) {
return -1;
}
// 找到元素,后续元素向前移动一位(覆盖被删除元素)
// 内存拷贝:从find_idx+1位置开始,拷贝(*nmemb - find_idx - 1)个int元素
memmove(&(*arr)[find_idx], &(*arr)[find_idx+1],
(*nmemb - find_idx - 1) * sizeof(int));
(*nmemb)--; // 已存储个数-1
// 可选:容量过大时缩容(如已存储个数 < 容量/2,缩容为原容量-INCREASE)
if (*nmemb < *capacity - INCREASE && *capacity > INCREASE) {
int new_cap = *capacity - INCREASE;
int *new_arr = (int *)realloc(*arr, new_cap * sizeof(int));
if (new_arr != NULL) { // 缩容失败不影响,继续使用原内存
*arr = new_arr;
*capacity = new_cap;
}
}
return 0; // 删除成功
}
3. 遍历打印函数 show_elm
void show_elm(const int *arr, int nmemb) {
// 入参合法性检查
if (arr == NULL || nmemb <= 0) {
printf("数组为空或无有效元素\n");
return;
}
// 遍历打印所有元素
printf("动态数组元素:");
for (int i=0; i<nmemb; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
4.4.4 测试主函数(完整使用流程)
int main() {
int *arr = NULL; // 动态数组指针,初始化为NULL
int capacity = 0; // 数组容量,初始化为0
int nmemb = 0; // 已存储元素个数,初始化为0
int ret; // 函数返回值,用于判断操作结果
// 1. 尾插添加元素:10,20,30,40,50,60(触发一次扩容,容量从5→10)
int data[] = {10,20,30,40,50,60};
for (int i=0; i<sizeof(data)/sizeof(int); i++) {
ret = add_elm(&arr, &capacity, &nmemb, data[i]);
if (ret != 0) {
printf("添加元素%d失败,错误码:%d\n", data[i], ret);
// 失败时释放已开辟的内存,避免内存泄漏
free(arr);
arr = NULL;
return -1;
}
}
printf("添加后:容量=%d,已存储个数=%d\n", capacity, nmemb);
show_elm(arr, nmemb); // 打印:10 20 30 40 50 60
// 2. 按值删除元素:20(存在)
ret = del_elm(&arr, &capacity, &nmemb, 20);
if (ret == 0) {
printf("删除20成功\n");
} else if (ret == -1) {
printf("未找到元素20\n");
} else {
printf("删除20失败,错误码:%d\n", ret);
}
printf("删除20后:容量=%d,已存储个数=%d\n", capacity, nmemb);
show_elm(arr, nmemb); // 打印:10 30 40 50 60
// 3. 按值删除元素:100(不存在)
ret = del_elm(&arr, &capacity, &nmemb, 100);
if (ret == -1) {
printf("未找到元素100,删除失败\n");
}
// 4. 继续添加元素:70,80,90,100,110(触发扩容,容量从10→15)
int data2[] = {70,80,90,100,110};
for (int i=0; i<sizeof(data2)/sizeof(int); i++) {
add_elm(&arr, &capacity, &nmemb, data2[i]);
}
printf("继续添加后:容量=%d,已存储个数=%d\n", capacity, nmemb);
show_elm(arr, nmemb); // 打印:10 30 40 50 60 70 80 90 100 110
// 5. 释放堆内存,避免内存泄漏
free(arr);
arr = NULL;
capacity = 0;
nmemb = 0;
return 0;
}
4.4.5 运行结果(预期输出)
添加后:容量=10,已存储个数=6
动态数组元素:10 20 30 40 50 60
删除20成功
删除20后:容量=10,已存储个数=5
动态数组元素:10 30 40 50 60
未找到元素100,删除失败
继续添加后:容量=15,已存储个数=10
动态数组元素:10 30 40 50 60 70 80 90 100 110
4.4.6 实战核心要点
- 二级指针的必要性:扩容时 realloc 返回新地址,必须通过二级指针
**arr修改一级指针arr的指向,否则一级指针仍指向原地址(野指针); - 内存扩容的安全性:用临时指针接收 realloc 的返回值,避免扩容失败导致原地址丢失,造成内存泄漏;
- 入参合法性检查:所有函数首先检查入参是否合法,避免空指针、非法数值导致的程序崩溃;
- 内存泄漏规避:任何操作失败时,必须释放已开辟的堆内存,避免内存泄漏;
- 释放后置 NULL:堆内存释放后,立即将指针置 NULL,容量和个数置 0,避免野指针。
五、进阶知识点总结(必背核心)
- 万能指针 void*:可存储任意类型地址,无运算能力,使用前必须强制类型转换;
- NULL 宏:本质是
(void*)0,指针的标准初始化值,解引用前必须判空; - 野指针:指向无效地址的指针,产生于未初始化、内存释放后未置 NULL、指针越界,规避核心是 “初始化 + 判空 + 释放后置 NULL”;
- 二级指针 int**:存储一级指针的地址,用于修改一级指针的指向,内存大小 8 字节(64 位),运算能力 = sizeof (int*)=8 字节;
- 堆内存特性:用户控制生命周期,空间大,需主动申请和释放,避免内存泄漏;
- 堆内存函数:malloc(基础开辟,未初始化)、calloc(开辟 + 初始化 0)、realloc(扩容 / 缩容,异地扩容需更新地址)、free(释放,后置 NULL);
- 动态数组核心:结合二级指针和 realloc 实现灵活扩容,入参检查和内存泄漏规避是必备规范。
六、高频面试题(校招必考,附答案)
1. void * 指针为什么不能进行运算?
答:指针运算的本质是按类型偏移字节数,void * 是无类型指针,编译器无法确定其指向的数据类型,因此无法计算偏移的字节数,故不支持任何运算。
2. 野指针和 NULL 指针的区别?
答:① NULL 指针是显式初始化的空指针,本质是 0 地址,可通过if (p!=NULL)检测;② 野指针是存储随机地址的无效指针,无法被编译器检测,解引用会导致程序崩溃;③ NULL 指针是安全的(不解引用即可),野指针是极度危险的。
3. 为什么动态开辟数组需要使用二级指针?
答:动态数组扩容时,realloc 可能会异地扩容(重新申请新内存,释放原内存),返回新的内存地址。一级指针的值是原地址,无法在函数内部修改,因此需要通过二级指针指向一级指针,在函数内部修改一级指针的指向,使其指向扩容后的新地址。
4. malloc 和 calloc 的区别?
答:① 初始化:malloc 申请的内存未初始化,是随机垃圾值;calloc 申请的内存自动初始化为 0;② 参数:malloc 接收一个参数(总字节数);calloc 接收两个参数(元素个数 + 每个元素的字节数);③ 适用场景:malloc 适合普通内存开辟;calloc 适合动态数组开辟(无需手动初始化)。
5. realloc 扩容失败会有什么后果?如何避免?
答:① 后果:realloc 扩容失败时返回 NULL,原内存地址不会被释放,若直接写arr = realloc(arr, size),会导致原地址丢失,造成内存泄漏;② 避免方法:用临时指针接收 realloc 的返回值,判空后再将临时指针赋值给原指针,扩容失败时释放原内存。
6. 什么是内存泄漏?如何避免?
答:① 内存泄漏:堆内存被申请后,用户程序忘记释放,导致该内存一直被占用,直到程序退出,最终造成内存耗尽;② 避免方法:① 堆内存申请和释放成对使用;② 函数操作失败时,释放已开辟的内存;③ 程序退出前,释放所有动态开辟的内存;④ 使用工具检测(如 valgrind)。
144

被折叠的 条评论
为什么被折叠?



