8. 指针的指针:二级指针到底有什么用?

说实话,很多C语言学习者学到指针就卡住了。等学到二级指针,更是直接懵圈。

“指针不就是存地址吗?那指针的指针,不就是存地址的地址?这玩意儿到底有啥用?”

嗯,我当年学的时候也这么想过。直到我在项目中栽了个大跟头,才真正明白二级指针的价值。

8.1 先搞清楚:二级指针到底是什么?

咱们先别急着上代码。你想想看,一级指针存的是普通变量的地址。那二级指针呢?它存的是指针变量的地址

画个图就清楚了:

变量 a 值: 42 指针 p 存 &a 二级指针 pp 存 &p 普通变量 一级指针(存地址) 二级指针(存指针的地址)

说白了,二级指针就是指向指针的指针。它不直接指向数据,而是指向一个“存着数据地址”的指针变量。

核心理解:
int a = 42; // 普通变量
int *p = &a; // 一级指针,存 a 的地址
int **pp = &p; // 二级指针,存 p 的地址

8.2 二级指针最常见的两个用途

我做了这么多年嵌入式开发,二级指针其实就两个核心用途。搞懂这两个,你就掌握了。

用途一:在函数内部修改指针本身

这是最经典的应用场景。你想想看,C语言函数传参是值传递。如果你传一个指针进去,函数里能改指针指向的内容,但改不了指针本身

举个例子:

// 错误示范:想通过函数修改指针
void alloc_memory_wrong(int *p) {
    p = (int *)malloc(sizeof(int) * 10);
    // 这里改的是 p 的副本,外面的指针没变!
}

// 正确做法:用二级指针
void alloc_memory_correct(int **pp) {
    *pp = (int *)malloc(sizeof(int) * 10);
    // *pp 就是外面的那个指针,这下改到了
}

int main() {
    int *arr = NULL;
    alloc_memory_correct(&arr);  // 传指针的地址
    // arr 现在指向了堆内存
    return 0;
}
⚠️ 我曾经踩过的坑: 刚入行时写链表,想在函数里给头指针分配内存。传了一级指针进去,结果函数返回后头指针还是 NULL。查了半天才发现,我改的只是形参的副本。从那以后,凡是需要在函数里“改变指针指向”的,我必用二级指针。

用途二:操作指针数组

比如你有一个字符串数组:

char *names[] = {"Alice", "Bob", "Charlie"};
// names 本身是一个数组,每个元素是 char* 指针
// 要遍历或排序这个数组,用二级指针就很自然

void sort_names(char **arr, int count) {
    // arr 就是二级指针,指向数组首元素
    // 每个元素 arr[i] 是一个 char*
    for (int i = 0; i < count - 1; i++) {
        for (int j = i + 1; j < count; j++) {
            if (strcmp(arr[i], arr[j]) > 0) {
                char *tmp = arr[i];
                arr[i] = arr[j];
                arr[j] = tmp;
            }
        }
    }
}

你看,这里 char **arr 就是二级指针。它指向一个指针数组的首元素。这种写法在嵌入式里处理配置表、命令表时特别常见。

8.3 二级指针与多维数组

很多人把二维数组和二级指针搞混。我明确告诉你:它们不是一回事

特性 二维数组 int a[3][4] 二级指针 int **p
内存布局 连续的一块内存 可能不连续,每行单独分配
类型 int (*)[4] int **
能否用 a[i][j] 可以 可以(前提是正确分配)
函数传参 需要指定列数 直接传二级指针
💡 我的建议: 在嵌入式开发中,如果数组大小固定,用二维数组更高效(内存连续,缓存友好)。如果大小动态变化,或者每行长度不同,用二级指针更灵活。

8.4 避坑指南:二级指针的常见错误

我见过太多人在二级指针上翻车。这里列几个最常见的:

  1. 忘记初始化:声明了 int **pp 就直接用,结果野指针崩溃。
  2. 混淆 *pp 和 **pp*pp 是一级指针,**pp 才是最终的数据。
  3. 内存泄漏:用二级指针动态分配了二维结构,释放时只释放了外层。
// 错误示例:只释放了外层
int **matrix = (int **)malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
    matrix[i] = (int *)malloc(cols * sizeof(int));
}
free(matrix);  // ❌ 内层每行的内存没释放!

// 正确释放
for (int i = 0; i < rows; i++) {
    free(matrix[i]);  // 先释放每行
}
free(matrix);         // 再释放行指针数组

8.5 总结一下

二级指针其实没那么玄乎。你只要记住两件事:

  • 它存的是指针变量的地址
  • 它主要用来在函数里修改指针本身,或者操作指针数组

我个人习惯在写链表、树这类数据结构时,大量使用二级指针。尤其是需要修改头指针的场景,二级指针比返回值更直观、更安全。

嗯,二级指针就讲这么多。你只要动手写几个例子,很快就能掌握。

一句话记住:
一级指针改数据,二级指针改指针。

公众号:蓝海资料掘金营,微信 deep3321