指针的算术运算:加减整数、指针相减与比较

指针的算术运算,说白了就是让指针在内存里「走路」。

很多新手觉得这玩意儿简单,不就是加加减减嘛。但我在项目中见过太多人栽在这上面——数组越界、段错误、数据被莫名篡改……十有八九都是指针算术搞的鬼。

今天咱们就把这块彻底掰扯清楚。

指针加减整数:往前走几步?

先看最基础的操作: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 内存)的指针才能进行 <、> 这类关系比较。指向不同对象的指针,只能用 == 和 != 判断是否相等。

注意:对指向不同数组的指针做 < 或 > 比较,结果是未定义的。编译器可能给你一个看似合理的结果,但换个平台就完全不一样了。

知识体系总览

下面这张图把指针算术运算的核心逻辑串起来了:

指针算术运算知识体系 指针 ± 整数 指针 - 指针 指针比较 移动 n × sizeof(类型) 字节 可用于遍历数组元素 越界解引用 = 未定义行为 结果类型:ptrdiff_t 表示元素个数差 必须指向同一数组/内存块 支持 < > <= >= == != 关系比较需同数组 相等比较可用于任意指针 核心原则:指针运算必须基于同一内存对象

避坑指南

讲完理论,咱们聊聊实战中容易踩的坑。

  • void* 不能做算术运算——因为编译器不知道它指向什么类型,不知道移动多少字节。有些编译器会报错,有些会默认按 1 字节移动,非常坑。
  • 数组名不是指针变量——arr++ 是错的,因为数组名是常量。但 arr + 1 可以,它返回一个新地址。
  • 指针相减的结果可能是负数——如果前面的指针地址更大,结果就是负的。用 ptrdiff_t 接收没问题,但别误赋值给 size_t。

我的习惯:每次做指针加减前,先确认指针当前指向哪里,目标位置是否在合法范围内。写完后用 assert 加个边界检查,能省去很多调试时间。

我曾经接手过一个遗留项目,里面有一段代码用指针遍历结构体数组,结果因为结构体对齐问题,指针每次移动的字节数算错了,导致读取的数据全是乱的。后来我改用 offsetof 宏来算偏移量,才彻底解决。

指针算术运算本身不复杂,复杂的是你永远不知道指针指向的内存是否还活着。所以,每次做运算前,问自己一句:这个指针还合法吗?

可可精品资料库