Day 09:C 语言指针进阶:万能指针、野指针、二级指针与动态内存分配(超详细完整版)
摘要本文是C 语言指针系列终极进阶篇承接上一篇指针基础重点讲解万能指针 (void*)、NULL 指针、野指针、二级指针、堆内存动态开辟四大核心难点。全文包含底层原理、内存图解、代码示例、面试高频考点、动态数组实战内容深度适配 CSDN 发布标准适合 C 语言进阶、校招笔试、面试复习、工程开发学习。一、万能指针void *a. 定义与本质万能指针就是void *类型的指针也叫无类型指针。它可以接收、存储任意类型数据的地址int、char、float、结构体、指针等它是 C 语言中唯一能兼容所有地址类型的指针b. 两大核心特性必考可以存储任意类型的地址int a 10; char ch w; int *p a; void *vp1 a; // 保存int地址 void *vp2 ch; // 保存char地址 void *vp3 p; // 保存指针地址没有任何运算能力、-、、-- 都不行原因void没有确定大小sizeof(void)未定义指针运算必须知道步长void*不知道指向多大数据禁止运算口诀万能存地址不能做运算要用强转先c. NULL 宏空指针本质#define NULL ((void *)0)本质是(void) 类型的 0 地址*表示指针不指向任何有效空间使用场景指针初始化时必须初始化为NULL释放动态内存后指针赋值为NULL防止野指针产生int *p NULL; // 标准、安全的初始化二、野指针极其危险a. 定义野指针 指向非法 / 无效 / 已释放内存地址的指针地址是随机值指向空间无权限访问操作野指针 程序崩溃、段错误 (segmentation fault)b. 野指针产生的三大原因指针定义后未初始化int *p; // 随机地址 → 野指针动态内存free释放后指针未置空int *p malloc(4); free(p); // p 仍然指向原堆空间 → 野指针指针越界访问数组 / 内存c. 规避方法指针必须初始化NULLfree 后立刻pNULL严格检查地址有效性三、二级指针指针的指针a. 定义二级指针专门用来存储 一级指针变量地址 的指针规则指针永远存储地址类型由所存地址的类型决定c运行int a 10; int *p a; // 一级指针存int地址 int **q p; // 二级指针存一级指针地址q就是二级指针类型int **b. 二级指针大小所有指针类型大小只与系统位数有关64 位系统8 字节32 位系统4 字节int **q; printf(%zu, sizeof(q)); // 输出 8c. 二级指针运算能力运算步长公式*指针 1 偏移 sizeof (去掉一个)**int **q; 去掉一个* → int* 运算能力 sizeof(int*) 8所以q1→ 地址偏移8 字节d. 二级指针解引用重点*q→ 取到一级指针p的值即a的地址**q→ 取到变量a的值10四、动态内存开辟堆空间管理栈空间太小、自动释放、无法控制生命周期 →必须用堆a. 堆空间三大特点手动开辟手动释放生命周期由程序员控制不释放就一直占用空间大适合大型数组、结构体、链表等b. 四大核心函数必须包含头文件#include stdio.h #include stdlib.h1. malloc —— 申请堆内存void *malloc(size_t size);功能申请size字节连续堆空间特点不初始化内容随机int *p (int *)malloc(10 * sizeof(int));2. calloc —— 申请并清零void *calloc(size_t nmemb, size_t size);申请nmemb个每个size字节自动初始化为 03. realloc —— 扩容 / 缩容void *realloc(void *ptr, size_t new_size);对已有堆空间重新调整大小自动拷贝原有数据4. free —— 释放内存void free(void *ptr);必须成对使用malloc/calloc/realloc释放后必须ptrNULL五、实战动态整型数组增删查完整版宏定义#define INCREASE 5 // 每次扩容5个int1. 添加元素自动扩容int add_elm(int **arr, int *capacity, int *nmemb, int data) { // 满了就扩容 if (*nmemb *capacity) { int *new_arr (int *)realloc(*arr, (*capacity INCREASE) * sizeof(int)); if (new_arr NULL) { perror(realloc fail); return -1; } *arr new_arr; *capacity INCREASE; } (*arr)[*nmemb] data; (*nmemb); return 0; }2. 删除元素按值删除int del_elm(int **arr, int *capacity, int *nmemb, int key) { int i, j; for (i 0; i *nmemb; i) { if ((*arr)[i] key) { // 后面元素向前覆盖 for (j i; j *nmemb - 1; j) { (*arr)[j] (*arr)[j 1]; } (*nmemb)--; return 0; } } return -1; // 没找到 }3. 遍历数组void show_elm(const int *arr, int nmemb) { for (int i 0; i nmemb; i) { printf(%d , arr[i]); } printf(\n); }4. main 函数测试int main() { int *arr NULL; int capacity 0; int nmemb 0; add_elm(arr, capacity, nmemb, 10); add_elm(arr, capacity, nmemb, 20); add_elm(arr, capacity, nmemb, 30); show_elm(arr, nmemb); // 10 20 30 del_elm(arr, capacity, nmemb, 20); show_elm(arr, nmemb); // 10 30 free(arr); arr NULL; return 0; }六、本章核心总结面试必背void* 是万能指针可存任意地址但不能运算。NULL (void)0*指针必须初始化 NULL。野指针非常危险未初始化、释放后未置空都会产生。二级指针存储一级指针地址int**1 偏移 8 字节。堆内存手动开辟、手动释放四大函数malloc/calloc/realloc/free。动态数组必须用二级指针传递才能在函数内修改数组首地址。