类型 *指针名; int *p1; // 指向 int 的指针 / pointer to int char *p2; // 指向 char 的指针 / pointer to char double *p3; // 指向 double 的指针 / pointer to double
多个指针同行声明的坑:
1 2
int* a, b; // ⚠️ a 是 int*,b 只是 int!/ a is int*, b is just int! int *a, *b; // ✅ 两个都是 int* / both are int*
推荐风格:int *p 而不是 int* p,让 * 紧贴变量名,避免歧义
3.2 初始化
未初始化的指针是野指针,必须立即初始化
1 2 3 4 5 6 7 8 9 10 11 12 13
/* ❌ 危险:野指针,指向随机地址 / dangling: points to random address */ int *p; *p = 10; // 未定义行为 / undefined behavior
/* ✅ 初始化为 NULL,明确表示"不指向任何地方" */ int *p = NULL;
/* ✅ 初始化为某个变量的地址 */ int a = 42; int *p = &a;
/* ✅ 初始化为动态申请的内存 */ int *p = malloc(sizeof(int));
3.3 NULL 与 0
1 2 3
int *p = NULL; // ✅ 标准写法 / standard int *p = 0; // ✅ 也合法,等价 / also valid, equivalent int *p = (void*)0; // NULL 的本质 / what NULL actually is
NULL 在 <stddef.h> 中定义,通常是 ((void*)0)
四、取地址 & 与解引用 *
这是一对互逆操作
4.1 取地址 &
&变量 返回变量的地址
1 2
int a = 42; printf("%p\n", (void*)&a); // 输出 a 的地址 / prints a's address
4.2 解引用 *
*指针 返回指针所指地址处的值
1 2 3 4 5
int a = 42; int *p = &a; printf("%d\n", *p); // 输出 42 / prints 42 *p = 100; // 修改 p 所指内容,即修改 a / modifies a through p printf("%d\n", a); // 输出 100 / prints 100
4.3 互逆关系
1 2 3 4 5 6
int a = 42; int *p = &a;
*p == a; // ✅ 解引用 p 等于 a 本身 / *p equals a &(*p) == &a; // ✅ 取 *p 的地址等于 &a / address of *p equals &a *(&a) == a; // ✅ 取 a 的地址再解引用等于 a / dereferencing &a gives a
口诀:* 和 & 互相抵消
五、NULL 指针与野指针
5.1 NULL 指针
NULL 是一个明确表示”不指向任何地方”的特殊值(通常是地址 0)
1 2 3 4 5
int *p = NULL; if (p == NULL) { // 安全:可以判断 / safe: can be tested } *p = 10; // ❌ 解引用 NULL 是未定义行为,通常导致段错误 / segfault
为什么需要 NULL?
表达”这个指针目前没有有效目标”
函数返回 NULL 表示失败(如 malloc、fopen)
防御性编程:free 后置 NULL,避免悬空
5.2 野指针(Wild Pointer)
指向未知/无效内存的指针
三种来源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* 来源 1:未初始化 / uninitialized */ int *p; // p 的值是随机的 / p contains garbage *p = 10; // ❌ 未定义行为 / undefined behavior
/* 来源 2:释放后未置空(悬空指针) / use-after-free (dangling) */ int *p = malloc(sizeof(int)); free(p); // 内存已归还系统 / memory returned to OS *p = 10; // ❌ 仍在使用已释放的内存 / use-after-free
/* 来源 3:指向已销毁的局部变量 / pointing to destroyed stack variable */ int *bad(void) { int x = 10; return &x; // ❌ 函数返回后 x 销毁 / x destroyed on return }
5.3 防御性编程
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* 1. 声明时立即初始化 / initialize at declaration */ int *p = NULL;
/* 2. 使用前检查 NULL / check before use */ if (p != NULL) { *p = 10; }
/* 3. free 后立即置 NULL / nullify after free */ free(p); p = NULL;
/* 4. free(NULL) 是安全的,无需判断 / free(NULL) is safe */ free(p); // 即使 p 是 NULL 也没问题 / OK even if p is NULL
六、指针算术
6.1 加减整数
p + n 表示移动 n × sizeof(*p) 字节,不是 n 字节
1 2 3 4 5 6 7 8 9
int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; // p 指向 arr[0] / p points to arr[0]
p + 1; // 指向 arr[1],地址 +4(int=4字节) / arr[1], address +4 p + 3; // 指向 arr[3],地址 +12 / arr[3], address +12 p - 1; // 指向 arr[-1](越界,未定义) / out of bounds arr: [10][20][30][40][50] ↑ ↑ ↑ ↑ ↑ p p+1 p+2 p+3 p+4
6.2 自增自减
1 2 3 4 5
int *p = arr; p++; // p 移动到下一个元素 / p advances to next element p--; // p 移动到上一个元素 / p backs up *p++; // 先解引用,再 p++(运算符优先级) / dereference then increment (*p)++; // 解引用后的值 +1 / increment the pointed-to value
6.3 两指针相减
指向同一数组的两个指针相减,结果是元素个数之差(不是字节数):
1 2 3 4 5
int arr[5] = {10, 20, 30, 40, 50}; int *p = &arr[1]; int *q = &arr[4];
ptrdiff_t diff = q - p; // 结果为 3 / result is 3
两指针相减返回 ptrdiff_t 类型(在 <stddef.h> 中定义)
1 2 3
/* 不在同一数组内的指针相减是未定义行为 / UB if not in same array */ int a, b; &b - &a; // ❌ 未定义 / undefined
6.4 不能做的运算
1 2 3
p + q; // ❌ 两个指针不能相加 / pointers can't be added p * 2; // ❌ 指针不能乘除 / no multiplication/division p + 1.5; // ❌ 只能加减整数 / only integer arithmetic
6.5 指针比较
同一数组内的指针可以比较大小:
1 2 3 4 5 6 7
int arr[5]; int *p = &arr[1]; int *q = &arr[3];
p < q; // ✅ true p == q; // ✅ 检查是否指向同一位置 / same location? p != NULL; // ✅ NULL 比较任何时候都合法 / NULL comparison always OK
七、指针与数组
这是 C 语言最容易混淆的部分
7.1 数组名的本质
数组名在大多数场合会”退化”为指向首元素的指针
1 2 3 4
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // arr 退化为 &arr[0] / arr decays to &arr[0] int *p = &arr[0]; // 等价 / equivalent
/* 函数声明 const,告诉调用方:我不会改你的数据 */ /* Declares const to signal: I won't modify your data */ voidprint_array(constint *arr, int len) { for (int i = 0; i < len; i++) printf("%d ", arr[i]); /* arr[0] = 99; ❌ 编译器拦住 / compiler blocks this */ }
十、二级指针与多级指针
10.1 概念
指向指针的指针
1 2 3 4 5 6 7
int a = 7; int *p = &a; // p 存 a 的地址 / p holds a's address int **pp = &p; // pp 存 p 的地址 / pp holds p's address 内存示意: [0x1000] pp = 0x2000 [0x2000] p = 0x3000 [0x3000] a = 7
10.2 逐层解引用
每个 * 穿透一层,只影响那一层:
1 2 3 4 5 6 7 8
pp // pp 本身(一个地址)/ pp itself *pp // p 本身(也是一个地址,等于 &a)/ p itself **pp // a 的值(7)/ a's value
/* 修改时同理 */ pp = NULL; // 改 pp 本身 / modify pp *pp = &b; // 改 p(让 p 指向 b) / modify p **pp = 99; // 改 a 的值 / modify a's value
10.3 典型用途 1:函数内修改外部指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* 让 caller 的指针指向新分配的内存 / let caller's pointer point to new memory */ voidcreate_array(int **out, int len) { *out = malloc(sizeof(int) * len); for (int i = 0; i < len; i++) (*out)[i] = i * 10; }
intmain(void) { int *arr = NULL; create_array(&arr, 5); /* arr 现在指向分配的数组 / arr now points to allocated array */ for (int i = 0; i < 5; i++) printf("%d ", arr[i]); free(arr); return0; }
10.4 典型用途 2:argv
1 2 3 4 5 6 7 8 9 10 11
intmain(int argc, char **argv) { /* argv 是 char**:指向 char* 数组的指针 */ /* argv is char**: pointer to array of char* */ for (int i = 0; i < argc; i++) printf("argv[%d] = %s\n", i, argv[i]); return0; } argv ──► [argv[0]] ──► "program_name" [argv[1]] ──► "arg1" [argv[2]] ──► "arg2" [NULL]
10.5 三级及以上
1 2 3 4 5 6
int a = 1; int *p = &a; int **pp = &p; int ***ppp = &pp;
***ppp = 99; // 三次解引用,修改 a / three derefs modify a
int arr1[5] = {1, 2, 3, 4, 5}; int arr2[5]; memcpy(arr2, arr1, sizeof(arr1)); // 不关心类型,按字节复制 / type-agnostic byte copy
3. 实现泛型容器:
1 2 3 4 5
typedefstruct { void *data; // 任意类型的元素 / generic element type size_t size; // 单个元素字节数 / size per element size_t count; } GenericArray;
十三、函数指针
13.1 函数也有地址
每个函数在内存中都有一个起始地址,函数名就是函数的地址(类似数组名)
1 2 3 4
intadd(int a, int b) { return a + b; }
printf("%p\n", (void*)add); // 函数 add 的地址 / address of add printf("%p\n", (void*)&add); // 同上 / same as above
13.2 函数指针声明
1 2 3 4 5 6 7
返回类型 (*指针名)(参数类型列表); /* 指向 int(int, int) 的函数指针 / pointer to int(int, int) */ int (*fp)(int, int);
/* ⚠️ 括号不能省 / parentheses are mandatory */ int *fp(int, int); // ❌ 这是函数声明,返回 int* / function returning int* int (*fp)(int, int); // ✅ 这才是函数指针 / actual function pointer
13.3 赋值与调用
1 2 3 4 5 6 7 8 9 10 11 12 13
intadd(int a, int b) { return a + b; } intmul(int a, int b) { return a * b; }
int (*fp)(int, int);
fp = add; // ✅ 函数名自动取地址 / function name decays fp = &add; // ✅ 显式取地址,等价 / explicit, equivalent
intadd(int a, int b) { return a + b; } intsub(int a, int b) { return a - b; } intmul(int a, int b) { return a * b; } intdivi(int a, int b) { return a / b; }
BinaryOp ops[] = {add, sub, mul, divi};
for (int i = 0; i < 4; i++) printf("%d\n", ops[i](10, 3)); // 13, 7, 30, 3
13.6 回调函数
函数指针最经典的用途:让 caller 把自定义逻辑传给被调用方
1 2 3 4 5 6 7 8 9
/* qsort 标准库示例 / qsort standard library example */ intcompare_int(constvoid *a, constvoid *b) { return *(int*)a - *(int*)b; }
int arr[] = {3, 1, 4, 1, 5, 9, 2, 6}; qsort(arr, 8, sizeof(int), compare_int); /* qsort 调用 compare_int 来比较元素 */ /* qsort calls compare_int to compare elements */
int arr[5]; arr[5] = 10; // ❌ 越界,未定义行为 / out of bounds, UB arr[-1] = 10; // ❌ 越界 / out of bounds
/* ✅ 边界检查 / bounds check */ int i = ...; if (i >= 0 && i < 5) arr[i] = 10;
17.5 整数溢出导致分配错误
1 2 3 4 5 6 7 8 9
size_t n = ...; int *p = malloc(n * sizeof(int)); // ⚠️ n 太大时溢出 / may overflow
/* ✅ 检查溢出 / check overflow */ if (n > SIZE_MAX / sizeof(int)) { /* 太大 / too large */ return-1; } int *p = malloc(n * sizeof(int));
17.6 完整的防御性代码模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
intsafe_function(int len) { /* 1. 参数检查 / validate inputs */ if (len <= 0 || len > MAX_LEN) return-1;
/* 2. 分配并检查 / allocate and check */ int *p = malloc(sizeof(int) * len); if (p == NULL) return-1;
/* 3. 使用 / use */ for (int i = 0; i < len; i++) p[i] = i;
/* 4. 释放并置 NULL / free and nullify */ free(p); p = NULL;
return0; }
十八、复杂指针声明的解读
18.1 顺时针螺旋法则
从变量名开始,按右→上→左→下螺旋方向阅读
18.2 经典例子
1 2 3 4 5 6 7
int *p; // p 是指向 int 的指针 / pointer to int int **p; // p 是指向「int 指针」的指针 / pointer to pointer to int int *p[10]; // p 是「10 个 int* 的数组」/ array of 10 pointers to int int (*p)[10]; // p 是「指向 10 个 int 数组」的指针 / pointer to array of 10 ints int (*p)(int); // p 是「参数为 int、返回 int 的函数指针」/ pointer to function int *(*p)(int); // p 是「参数为 int、返回 int* 的函数指针」/ ptr to func returning int* int (*p[10])(int); // p 是「10 个函数指针的数组」/ array of 10 function pointers
18.3 用 cdecl 工具辅助
1 2
$ echo'explain int (*p[10])(int)' | cdecl declare p as array 10 of pointer to function (int) returning int