指针越界排查与修复实战 - 第2节

指针的算术运算:加减整数

指针能加减整数,这是C/C++里最基础也最容易踩坑的地方。

我刚开始学的时候,总觉得指针加减就是地址的加减。比如 int* p,p+1 不就是地址加1吗?

错。

实际上,p+1 加的是 sizeof(int) 个字节。在我的机器上,int 是4字节,所以地址会跳4个字节。

为什么会这样?

因为指针的类型决定了它的步长。说白了,编译器知道 p 指向的是 int,所以 p+1 自然要指向下一个 int 的起始位置。

核心公式:

指针 p 加上整数 n,实际地址 = p + n * sizeof(指针指向的类型)

看个例子:

int arr[] = {10, 20, 30, 40};
int* p = arr;  // p 指向 arr[0]

printf("%p\n", p);   // 比如 0x1000
printf("%p\n", p+1); // 0x1004,不是 0x1001
printf("%d\n", *(p+2)); // 输出 30

我在项目中遇到过一个问题:有人用 char* 去遍历 int 数组,结果每次只跳1个字节,数据全读错了。嗯,这就是类型不匹配的典型后果。

注意:指针加减整数时,如果指针指向数组之外,就是未定义行为。我曾经调试过一个崩溃,就是因为循环多跑了一次,指针越界了。

指针与数组的关系

数组名和指针,很多人觉得它们是一回事。其实不然。

数组名在大多数表达式中会隐式转换为指向首元素的指针。但数组名本身不是指针变量,它没有自己的存储空间。

你想想看:

int arr[5];
int* p = arr;  // 等价于 int* p = &arr[0]

// arr 和 p 在很多场景下可以互换使用
printf("%d\n", arr[2]);  // 没问题
printf("%d\n", p[2]);    // 也没问题

但有一个关键区别:

printf("%zu\n", sizeof(arr)); // 输出 20 (5个int)
printf("%zu\n", sizeof(p));   // 输出 8 (指针本身的大小,64位系统)

我建议你记住一句话:数组名是常量,指针是变量。你不能写 arr++,但可以写 p++。

个人经验:我习惯用指针遍历数组,而不是用下标。因为指针运算通常更快,而且代码更灵活。但要注意边界检查,别跑出去了。

指针比较运算

指针可以比较大小,前提是它们指向同一个数组(或同一个对象的成员)。

比较规则很简单:

  • p == q:指向同一个位置
  • p < q:p 指向的地址更小
  • p > q:p 指向的地址更大

看个实际用法:

int arr[5] = {1, 2, 3, 4, 5};
int* start = arr;
int* end = arr + 5;  // 指向最后一个元素之后

// 用指针比较来遍历
while (start < end) {
    printf("%d ", *start);
    start++;
}
// 输出:1 2 3 4 5

这里 end 指向的是数组末尾之后的一个位置,这叫"尾后指针"。C标准允许你取这个地址,但不能解引用它。

我曾经犯过一个错:把两个不同数组的指针拿来比较大小。结果在不同编译器上行为不一致,调试了半天才发现问题。记住,不同数组的指针比较是未定义行为。

指针比较还有一个常见用途:判断指针是否在某个范围内。

int* p = arr + 2;
if (p >= arr && p < arr + 5) {
    printf("p 在数组范围内\n");
}

这种写法在写内存池、缓冲区管理时特别有用。我自己的项目中就经常用这种方式做边界检查。

知识体系总览

下面这张图把本章的核心逻辑串起来了:

指针算术运算知识体系 指针算术运算 指针 ± 整数 步长 = sizeof(类型) 指针与数组 数组名 ≠ 指针变量 指针比较 同数组内比较才安全 地址偏移计算 类型决定步长 隐式转换 sizeof 区别 边界检查 尾后指针 核心原则:类型决定行为,边界决定安全

这张图把三个核心知识点串起来了。你看,指针加减整数、指针与数组的关系、指针比较运算,它们都围绕着一个核心:类型决定步长,边界决定安全。

我个人的习惯是,每次写指针运算前,先问自己三个问题:

  1. 这个指针的类型是什么?
  2. 它指向的数组范围是多少?
  3. 加减后会不会越界?

养成这个习惯,能帮你避开90%的指针越界问题。

避坑指南:我曾经在写一个网络协议解析器时,用指针遍历数据包。因为没注意类型转换后的步长变化,导致解析结果全乱了。后来我加了一行断言检查指针范围,问题就再没出现过。

好了,指针的算术运算就聊到这儿。记住:加减看类型,比较看范围,数组名不是变量。把这些基础打牢,后面讲指针越界排查时你才能游刃有余。

可可精品资料库