嵌入式Linux学习C语言(Day09)C 语言指针进阶详解(万能指针 + 野指针 + 二级指针 + 堆内存动态开辟)

点赞+关注私信领取全部代码资料~

一、万能指针(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 种方法可全覆盖规避野指针问题:

  1. 指针定义即初始化:要么指向有效地址,要么置 NULL(int *p = NULL;);
  2. 内存释放后立即置 NULLfree(p); p = NULL;,让指针成为可检测的空指针;
  3. 解引用前必判空:所有指针操作前加if (p != NULL)检查;
  4. 严格限制指针运算范围:遍历数组 / 字符串时,以长度或'\0'作为结束条件,避免越界。

三、二级指针(int**)—— 指针的指针(进阶核心)

二级指针是存储一级指针地址的指针,是指针进阶的核心内容,主要用于修改一级指针的指向(如堆内存动态扩容)、二维数组操作、** 命令行参数(argv)** 等场景,理解二级指针是掌握 C 语言内存操作的关键。

3.1 二级指针的核心原理

核心原则(继承一级指针)

  1. 指针的本质永远是存储内存地址,与普通指针无区别;
  2. 指针的类型由其存储的地址类型决定 —— 一级指针存储普通变量的地址,二级指针存储一级指针的地址。

通俗理解

  • 普通变量(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;

关键说明

  1. q二级指针变量,简称二级指针;
  2. q的类型是int**,表示 “指向 int * 类型指针的指针”,限制其只能存储int*类型一级指针的地址;
  3. 二级指针的解引用分两层*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*(一级)int8 字节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。

核心特性(重点!)
  1. ptr必须是malloc/calloc/realloc返回的堆内存地址,不可是栈内存地址;
  2. 扩容时,若原内存后有连续空闲空间,直接在原地址后扩容(原地扩容);若无,则重新申请一块新的 size 字节内存,将原数据拷贝到新内存,释放原内存(异地扩容)——因此 realloc 返回的新地址可能与原地址不同
  3. ptr=NULL,则 realloc 等价于 malloc(realloc(NULL, size) = malloc(size));
  4. 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指向的堆内存,将内存归还给系统,供其他程序申请使用。

核心使用规则(必守!)

  1. ptr必须是malloc/calloc/realloc返回的有效堆内存地址,不可释放栈内存、NULL、已释放的内存;
  2. 堆内存释放后必须将指针置 NULL,避免成为野指针;
  3. 动态开辟的内存必须成对使用(申请一次,释放一次),避免内存泄漏或重复释放;
  4. 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 实战需求与宏定义

核心需求
  1. 尾插添加:向动态数组末尾插入数据,数组容量不足时自动扩容
  2. 按值删除:删除数组中第一个匹配指定值的元素,后续元素前移;
  3. 遍历打印:遍历输出数组所有元素,原数组只读;
宏定义
#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 实战核心要点

  1. 二级指针的必要性:扩容时 realloc 返回新地址,必须通过二级指针**arr修改一级指针arr的指向,否则一级指针仍指向原地址(野指针);
  2. 内存扩容的安全性:用临时指针接收 realloc 的返回值,避免扩容失败导致原地址丢失,造成内存泄漏;
  3. 入参合法性检查:所有函数首先检查入参是否合法,避免空指针、非法数值导致的程序崩溃;
  4. 内存泄漏规避:任何操作失败时,必须释放已开辟的堆内存,避免内存泄漏;
  5. 释放后置 NULL:堆内存释放后,立即将指针置 NULL,容量和个数置 0,避免野指针。

五、进阶知识点总结(必背核心)

  1. 万能指针 void*:可存储任意类型地址,无运算能力,使用前必须强制类型转换;
  2. NULL 宏:本质是(void*)0,指针的标准初始化值,解引用前必须判空;
  3. 野指针:指向无效地址的指针,产生于未初始化、内存释放后未置 NULL、指针越界,规避核心是 “初始化 + 判空 + 释放后置 NULL”;
  4. 二级指针 int**:存储一级指针的地址,用于修改一级指针的指向,内存大小 8 字节(64 位),运算能力 = sizeof (int*)=8 字节;
  5. 堆内存特性:用户控制生命周期,空间大,需主动申请和释放,避免内存泄漏;
  6. 堆内存函数:malloc(基础开辟,未初始化)、calloc(开辟 + 初始化 0)、realloc(扩容 / 缩容,异地扩容需更新地址)、free(释放,后置 NULL);
  7. 动态数组核心:结合二级指针和 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)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值