多级指针 = 指向指针的指针;T** 即「指向 T*」,多解一次引用降一级。
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**,*pp 是 int*(即 p),**pp 是 int(即 x)。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 终止
一级指针能让函数改外部变量;二级指针能让函数改外部的指针。经典场景:函数内部分配内存,想把结果传出来:
// 错误: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**。
第 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] 了(泄漏)。
多级指针不是越多越好,滥用会让代码难以阅读。常见合理用途:
argv(char**);T** 出参);T**);void** 传回句柄)。三级以上(char***)极其少见,出现时先怀疑是不是该用 typedef 或结构体把它包起来。
**pp 编译成两次 mov(先 mov rax, [pp] 取 p,再 mov rbx, [rax] 取 x),开销随级数线性增长。String[] args 本质就是「引用数组」——引用指向数组,数组元素又是引用,就是隐式的两级间接。char** argv 与 C 一致;std::vector<std::string> 用两层对象封装了「字符串数组」。sys.argv 是 list[str],解释器把两级间接全藏起来了。