第十八天 C语言 Day18:二级指针(与指针数组等效)· main函数原型(argc/argv)· 常量指针与指针常量 · 野指针/空指针/空悬指针 · void与void*万能指针
第十八天 C语言 Day18:二级指针(与指针数组等效)· main函数原型(argc/argv)· 常量指针与指针常量 · 野指针/空指针/空悬指针 · void与void*万能指针
系列:粤嵌嵌入式培训学习笔记 (C语言篇-第18天)
上一篇:第十七天 C语言 Day17:字符指针与字符串 · 字符串作函数参数 · 指针函数 · 函数指针
环境:Ubuntu 22.04 · GCC · Vim | 2026年8月19日
① 概览
指针最后的收尾与避坑。先把指针家族八个易混概念一次性理清(指针变量/变量指针/指针函数/函数指针/数组指针/指针数组/数组元素指针/回调函数);再学二级指针——指向指针的指针,核心结论"二级指针与指针数组等效,与二维数组不等效";然后搞懂 main 函数原型
argc/argv(命令行传参);接着是三个"const 指针"的组合(常量指针/指针常量/常量指针常量)和三个"危险指针"(野指针/空指针/空悬指针);最后认识 void 与 void*(万能指针,malloc 返回的就是它)。
| 模块 | 一句话 |
|---|---|
| 指针家族八概念 ⭐ | 看 []() 和 * 的优先级定本质:谁先结合就是谁 |
| 二级指针 ⭐ | int **p 存一级指针地址;解引用 1 次得串、2 次得字符 |
| 与指针数组等效 ⭐ | char **p = 指针数组;但 int **k = 二维数组 编译报错 |
| main 原型 ⭐ | int main(int argc, char *argv[]),argc 参数个数、argv 参数数组 |
| 常量指针 ⭐ | const int *p 指向可改、值不可改(保护数据) |
| 指针常量 ⭐ | int *const p 指向不可改、值可改(必须初始化) |
| 野指针 ⭐ | 未初始化/已释放/越界,访问即 UB |
| 空悬指针 ⭐ | free 后没置 NULL,野指针特例 |
| void* ⭐ | 万能指针,解引用前必须强制转具体类型 |
② 指针家族八个易混概念
| 名称 | 本质 | 一句话说明 |
|---|---|---|
| 指针变量 | 变量 | 专门存放地址的变量 |
| 变量指针 | 地址 | 变量在内存中的地址,& 取得 |
| 指针函数 | 函数 | 返回值是指针的函数 |
| 函数指针 | 指针 | 指向函数的指针变量 |
| 数组指针 | 指针 | 指向整个数组的指针变量 |
| 数组元素指针 | 指针 | 指向数组某个元素的指针变量 |
| 指针数组 | 数组 | 元素为指针的数组 |
| 回调函数 | 函数 | 通过函数指针被间接调用的函数 |
记忆方法:看结合优先级
[]、() 优先级高于 *,谁先和名字结合,本质就是谁:
| 写法 | 结合过程 | 本质 |
|---|---|---|
int *p[5] |
p 先与 [5] 结合 |
数组 → 指针数组 |
int (*p)[5] |
括号让 p 先与 * 结合 |
指针 → 数组指针 |
int *f(void) |
f 先与 (void) 结合 |
函数 → 指针函数 |
int (*f)(void) |
括号让 f 先与 * 结合 |
指针 → 函数指针 |
③ 二级指针(多重指针)⭐
定义:存一级指针地址的指针,解引用两次才能访问原始数据。
int a = 10;
int *p = &a; // 一级指针,指向 a
int **w = &p; // 二级指针,指向 p
printf("*p = %d\n", *p); // 10(解引用1次得 a 值)
printf("*w = %p\n", *w); // a 的地址(解引用1次得 p 的值)
printf("**w = %d\n", **w); // 10(解引用2次得 a 值)
int ***k = &w; // 三级指针,指向 w
printf("***k = %d\n", ***k); // 10(解引用3次)
*p=10
*w=0x7ffc98125b1c
**w=10
💡 开发中最常用就是一级、二级指针,三级以上很少见。
3.1 二级指针与指针数组等效
char *str_arr[] = {"jiaojiao","panpan","leilei"}; // 指针数组(字符串数组)
char **p = str_arr; // 二级指针接收指针数组,等效
int len = sizeof(str_arr) / sizeof(str_arr[0]);
for (; p < str_arr + len; p++)
printf("%s\n", *p); // 解引用1次拿到字符串首地址,配合%s输出
jiaojiao
panpan
leilei
⚠️ char 型二级指针只需解引用 1 次就能拿到字符串地址;解引用 2 次才拿到单个字符。
3.2 二级指针遍历字符串/字符
char *str[3] = {"abc", "aaa034", "12a12a"};
char **p = str;
// 方式1:遍历所有字符串
for (p = str; p < str + 3; p++)
printf("%s\n", *p);
// 方式2:遍历每个字符串的所有字符
for (p = str; p < str + 3; p++)
{
while (**p != '\0') // 第二层循环,遍历到字符
{
printf("%c", **p);
(*p)++; // 字符偏移
}
printf("\t");
}
abc
aaa034
12a12a
abc aaa034 12a12a
💡 对一个字符串数组:解引用 1 次得字符串,解引用 2 次得字符。
3.3 二级指针与二维数组不等效
二维数组名是数组指针类型(int (*)[3]),不是二级指针:
int arr[][3] = {1, 3, 5, 11, 13, 15};
int (*p)[3] = arr; // 正确:数组指针
// int **k = arr; // 编译报错:int ** 与 int (*)[3] 类型不兼容
// printf("%d\n", **k); // 段错误
总结对照:
| 类型 | 本质 | 等效性 |
|---|---|---|
二级指针 int** |
指向指针的指针 | 与指针数组等效 |
指针数组 int *[] |
元素为指针的数组 | 退化为二级指针 |
二维数组 int[][3] |
数组的数组 | 与数组指针 int (*)[3] 等效 |
④ main 函数原型 ⭐
main 是程序入口函数,标准写法:
int main(int argc, char *argv[]) // 指针数组形式
int main(int argc, char **argv) // 二级指针形式(等价)
argc(argument count):参数个数,默认 1(只含程序名)argv(argument vector):参数数组,存所有参数的字符串
int main(int argc, char *argv[])
{
printf("外部参数个数:%d\n", argc);
for (int i = 0; i < argc; i++)
{
printf("%s\n", argv[i]); // 下标法
printf("%s\n\n", *(argv+i)); // 指针法
}
return 0;
}
$ ./a.out yueqian 192.168.30.131
外部参数个数:3
./a.out
./a.out
yueqian
yueqian
192.168.30.131
192.168.30.131
💡 两个等效记忆:二维数组 ⟷ 数组指针;二级指针 ⟷ 指针数组。
⑤ 常量指针与指针常量 ⭐
三个"const 指针"组合,本质区别在 const 相对 * 的位置。
5.1 常量指针(指向常量的指针)
const int *p——指向可改,指向的值不可改(保护数据)。
int a = 10;
const int *p = &a;
// *p = 100; // 错:指向的数据不可改
printf("*p=%d\n", *p); // 10
int b = 20;
p = &b; // 对:指向可以改
printf("*p=%d\n", *p); // 20
5.2 指针常量(指针本身是常量)
int *const p——指向不可改,指向的值可改(必须初始化)。
int a = 10;
int *const p = &a; // 定义时必须初始化
*p = 100; // 对:指向的值可变
printf("*p=%d\n", *p); // 100
int b = 20;
// p = &b; // 错:指向不可变
5.3 常量指针常量
const int *const p——指向、值都不可改(最严格,必须初始化)。
int a = 10;
const int *const p = &a;
// *p = 100; 错:值不可改
// p = &b; 错:指向不可改
printf("*p=%d\n", *p); // 10
总结对比 + 记忆
| 类型 | 语法 | 指向可变 | 值可变 | 核心用途 |
|---|---|---|---|---|
| 常量指针 | const int *p |
是 | 否 | 保护数据(函数参数) |
| 指针常量 | int *const p |
否 | 是 | 固定指向(全局指针) |
| 常量指针常量 | const int *const p |
否 | 否 | 完全只读 |
💡
const在*左侧 → 修饰数据(值不可改);const在*右侧 → 修饰指针(指向不可改)。 函数参数优先用常量指针void func(const int *p),防意外改数据。
⑥ 野指针、空指针、空悬指针 ⭐
6.1 野指针
定义:未初始化、指向非法内存的指针(未初始化/已释放/越界),访问触发未定义行为(UB)。
三大产生场景 + 规避:
// 场景1:未初始化 → 置 NULL
int *p = NULL;
if (p == NULL) { printf("指针未指向有效内存!\n"); return -1; }
// 场景2:指向已释放的内存 → 释放后置空
int *p = malloc(sizeof(int));
free(p);
p = NULL; // 立即置空
// 场景3:返回局部变量地址 → 避免返回栈地址
int* fun(int a, int b)
{
int sum = a + b; // 局部变量在栈上
return ∑ // 错:函数返回后 sum 销毁
}
通用规避四招:① 定义立即初始化(NULL 或有效地址)② 释放后立即置 NULL ③ 不返回局部变量地址 ④ 使用前非空校验。
6.2 空指针
定义:值为 NULL 的指针(NULL 本质是 0,地址 0x00000000,系统保留不可访问)。
int *p = NULL;
printf("空指针地址:%p\n", p); // 0x0
free(p); // 安全:free(NULL) 不报错(系统兼容处理)
p = NULL;
6.3 空悬指针
定义:指针指向的内存已释放,但指针本身没置空(野指针特例)。
char *p = (char*)malloc(100);
free(p); // 释放内存
printf("指针地址:%p\n", p); // 仍是非 NULL 地址(已失效)
// printf("%c\n", *p); // 危险:访问空悬指针 = UB
p = NULL; // 置空后变空指针,不再是空悬指针
⑦ void 与 void* ⭐
| 类型 | 本质 | 核心用途 |
|---|---|---|
void |
无类型/空类型 | 函数返回值/参数(表示"无") |
void* |
通用指针(万能指针) | 指向任意类型,跨类型兼容 |
7.1 void 的用法
void print_hello() // void 作返回值:无返回值
{
printf("hello world!\n");
// 正常结束省略 return;提前结束要写 return;
}
void func(void) // void 作参数:无参数
{
}
7.2 void* 的用法
- 可指向任意类型数据,但必须强制转换后才能解引用
- 仅支持指针赋值、地址偏移,不能直接解引用
- 常用于
malloc/memcpy、通用接口
#include <stdlib.h>
// 通用处理函数:接收/返回任意类型指针
void* process_data(void* p)
{
return p;
}
int main()
{
// 处理 int
int m = 10;
int *r1 = (int*)process_data(&m); // 强制转 int*
printf("Integer value:%d\n", *r1); // 10
// 处理 double
double pi = 3.14;
double *r2 = (double*)process_data(&pi);
printf("Double value:%lf\n", *r2); // 3.140000
// void* 用于 malloc(返回 void*)
void* mem = malloc(sizeof(int));
if (mem == NULL) { perror("malloc failed!"); return -1; }
int *p_mem = (int*)mem; // 强制转 int* 再解引用
*p_mem = 100;
printf("Malloc value:%d\n", *p_mem); // 100
free(mem);
return 0;
}
Integer value:10
Double value:3.140000
Malloc value:100
⚠️ 三条注意:①
void不能定义变量(void a;编译报错)②void*可与任意指针直接转换,但解引用前必须显式转具体类型 ③malloc返回的就是void*。
⑧ 踩坑记录 ⭐
坑1:二级指针接二维数组
- 现象:
int arr[][3]; int **k = arr;编译报错、运行段错误 - 根因:二维数组名是数组指针
int (*)[3],不是二级指针 - 解决:二维数组用
int (*p)[3] = arr;;指针数组才用int **p = 指针数组;
坑2:改字符指针指向的字符串常量
- 现象:
char *s = "abc"; s[1] = 'X';段错误 - 根因:字符串常量在只读区
- 解决:要改内容用字符数组
char s[] = "abc";
坑3:常量指针/指针常量搞反
- 现象:
const int *p和int *const p分不清谁可改 - 根因:
const位置决定——左侧修饰数据、右侧修饰指针 - 解决:记口诀"
*左值不可改,*右针不可改"
坑4:free 后没置 NULL
- 现象:free 后还解引用,程序随机崩溃
- 根因:free 只归还内存,指针还留着旧地址(空悬指针)
- 解决:
free(p); p = NULL;
坑5:void* 直接解引用
- 现象:
void *p; *p = 20;编译报错 - 根因:void* 不知道指向对象大小,无法解引用
- 解决:先强制转具体类型
*(int*)p = 20;
⑨ 小结
今天完成:指针家族八概念(本质 + 结合优先级记忆法)→ 二级指针(解引用1次得串/2次得字符、与指针数组等效、与二维数组不等效、遍历字符串/字符)→ main 函数原型(argc/argv 命令行传参)→ 常量指针/指针常量/常量指针常量(const 位置记忆法 + 对比表)→ 野指针/空指针/空悬指针(三大场景 + 规避四招)→ void 与 void*(万能指针,malloc 返回值)。
下一篇:动态内存管理——malloc/calloc/realloc/free、内存泄漏与越界访问、结构体入门。
本文首发于 CSDN,转载请注明出处。
更多推荐

所有评论(0)