第 7 章 · 多级指针

多级指针 = 指向指针的指针;T** 即「指向 T*」,多解一次引用降一级。

6.1 二级指针的解引用链

int  x  = 42;
int *p  = &x;     // p 指向 x
int **pp = &p;    // pp 指向 p

**pp;             // 两次解引用:先拿到 p,再拿到 x = 42
**pp = 99;        // 通过 pp 改 x
printf("%d\n", x);   // 99
 pp ──→ p ──→ x
int**   int*   int
  • pp 的类型是 int***ppint*(即 p),**ppint(即 x)。
  • 每一级指针都只是「存一个地址的变量」,所谓「多级」不过是地址里存的又是地址。

6.2 argv 就是 char**

main 的第二个参数是二级指针的经典例子:

int main(int argc, char **argv) { ... }
// 等价于 char *argv[],参数里数组退化成 char**

argv 是指针数组:每个 argv[i] 是一个 char*,指向一个命令行参数字符串:

argv ──→ argv[0] ──→ "./prog"
         argv[1] ──→ "foo"
         argv[2] ──→ "bar"
         argv[argc] ──→ NULL(终止哨兵)

遍历的两种写法:

for (int i = 0; i < argc; i++) puts(argv[i]);        // 下标
for (char **p = argv; *p; p++) puts(*p);             // 指针,靠末尾 NULL 终止

6.3 传二级指针:让函数改「指针」本身

一级指针能让函数改外部变量;二级指针能让函数改外部的指针。经典场景:函数内部分配内存,想把结果传出来:

// 错误:s 是局部副本,改了不影响外部指针
void bad_alloc(char *s) { s = malloc(64); }

// 正确:传 s 的地址,即 char**,改 *s 就是改外部指针
void good_alloc(char **s) {
    *s = malloc(64);
    if (!*s) return;       // 检查 malloc 失败
    strcpy(*s, "hi");
}

char *p = NULL;
good_alloc(&p);            // 传 &p,类型 char**
puts(p);                   // "hi"
free(p);

心法:「要改谁,就传谁的地址」。 要改 int,传 int*;要改 int*,传 int**;以此类推。&p 的类型就是 char**

6.4 动态分配指针数组(交错二维)

第 5 章的做法二就是二级指针。分配一个「每行长度可不同」的字符串数组:

int n = 3;
char **lines = malloc(n * sizeof(char*));
lines[0] = strdup("long line ...");   // strdup 内部 malloc + strcpy
lines[1] = strdup("s");
lines[2] = strdup("medium");
// 释放:先每行,再行指针数组
for (int i = 0; i < n; i++) free(lines[i]);
free(lines);

注意 lines[i]lines两层分配,释放顺序不能反,否则先 free(lines) 就再也找不到 lines[i] 了(泄漏)。

6.5 什么时候才需要多级指针

多级指针不是越多越好,滥用会让代码难以阅读。常见合理用途:

  • argvchar**);
  • 函数要改调用方的指针(T** 出参);
  • 动态二维/锯齿数组(T**);
  • 某些回调/句柄(void** 传回句柄)。

三级以上(char***)极其少见,出现时先怀疑是不是该用 typedef 或结构体把它包起来。

6.6 与汇编 / 其他语言对照

  • 汇编:多级指针就是多次间接寻址**pp 编译成两次 mov(先 mov rax, [pp] 取 p,再 mov rbx, [rax] 取 x),开销随级数线性增长。
  • Java:没有显式多级指针,但 String[] args 本质就是「引用数组」——引用指向数组,数组元素又是引用,就是隐式的两级间接。
  • C++char** argv 与 C 一致;std::vector<std::string> 用两层对象封装了「字符串数组」。
  • Pythonsys.argvlist[str],解释器把两级间接全藏起来了。