指针的算术运算:加减整数、指针相减与比较
指针的算术运算,说白了就是让指针在内存里「走路」。
很多新手觉得这玩意儿简单,不就是加加减减嘛。但我在项目中见过太多人栽在这上面——数组越界、段错误、数据被莫名篡改……十有八九都是指针算术搞的鬼。
今天咱们就把这块彻底掰扯清楚。
指针加减整数:往前走几步?
先看最基础的操作:ptr + n 或 ptr - n。
你可能会想,这不就是地址加 n 吗?错!
指针加减整数,实际移动的字节数是 n * sizeof(指针指向的类型)。举个例子:
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // p 指向 arr[0]
p = p + 2; // 实际地址增加了 2 * sizeof(int) = 8 字节
printf("%d\n", *p); // 输出 30
为什么不是加 2 个字节?因为编译器知道 p 是 int* 类型,它要保证每次移动都跳到下一个完整的 int 元素上。你想想看,如果只加 2 个字节,那读出来的数据就是半个 int,毫无意义。
核心要点:指针加减整数,移动的单位是「元素个数」,不是「字节数」。
我个人习惯在写循环遍历数组时,用指针加减来代替下标访问。比如:
int sum = 0;
int *end = arr + 5; // 指向数组末尾的下一个位置
for (int *p = arr; p < end; p++) {
sum += *p;
}
这样写的好处是——少了一个循环变量 i,代码更紧凑。但代价是你要格外小心边界条件。
警告:指针加减的结果如果越过了数组边界(包括末尾的下一个位置),对其进行解引用是未定义行为。编译器不会帮你检查,运行时可能直接崩溃。
指针与指针相减:距离有多远?
两个同类型的指针可以相减,结果是一个 ptrdiff_t 类型的整数,表示两个指针之间相差的元素个数。
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[1]; // 指向 2
int *p2 = &arr[4]; // 指向 5
ptrdiff_t diff = p2 - p1;
printf("%ld\n", diff); // 输出 3
这里 diff 是 3,因为 p2 和 p1 之间隔了 arr[2]、arr[3]、arr[4] 三个元素。
嗯,这里要注意:指针相减的前提是两个指针指向同一个数组(或者同一个 malloc 分配的内存块)。如果指向不同数组,结果毫无意义。
我曾经在调试一个内存池模块时,遇到过一个诡异的 bug——两个指针明明指向同一块内存,相减却得到负数。查了半天才发现,其中一个指针被意外修改成了另一个内存块的地址。这种问题用肉眼很难看出来,但用指针相减一算就露馅了。
小技巧:可以用指针相减来快速计算数组中的元素偏移量,比手动算下标更直观。
指针比较运算:谁在前?谁在后?
指针支持 <、<=、>、>=、==、!= 这些比较操作。
比较的是地址值的大小。地址值小的指针指向更低的内存地址,也就是「在前面」。
int arr[5] = {10, 20, 30, 40, 50};
int *p1 = &arr[0];
int *p2 = &arr[3];
if (p1 < p2) {
printf("p1 在 p2 前面\n"); // 这行会执行
}
比较运算最常见的用途就是判断指针是否在某个范围内,或者检查是否到达数组末尾。就像前面那个循环里写的 p < end。
但有个坑:只有指向同一个数组(或同一块 malloc 内存)的指针才能进行 <、> 这类关系比较。指向不同对象的指针,只能用 == 和 != 判断是否相等。
注意:对指向不同数组的指针做 < 或 > 比较,结果是未定义的。编译器可能给你一个看似合理的结果,但换个平台就完全不一样了。
知识体系总览
下面这张图把指针算术运算的核心逻辑串起来了:
避坑指南
讲完理论,咱们聊聊实战中容易踩的坑。
- void* 不能做算术运算——因为编译器不知道它指向什么类型,不知道移动多少字节。有些编译器会报错,有些会默认按 1 字节移动,非常坑。
- 数组名不是指针变量——
arr++是错的,因为数组名是常量。但arr + 1可以,它返回一个新地址。 - 指针相减的结果可能是负数——如果前面的指针地址更大,结果就是负的。用
ptrdiff_t接收没问题,但别误赋值给size_t。
我的习惯:每次做指针加减前,先确认指针当前指向哪里,目标位置是否在合法范围内。写完后用 assert 加个边界检查,能省去很多调试时间。
我曾经接手过一个遗留项目,里面有一段代码用指针遍历结构体数组,结果因为结构体对齐问题,指针每次移动的字节数算错了,导致读取的数据全是乱的。后来我改用 offsetof 宏来算偏移量,才彻底解决。
指针算术运算本身不复杂,复杂的是你永远不知道指针指向的内存是否还活着。所以,每次做运算前,问自己一句:这个指针还合法吗?