C 指针运算
更新时间:2026-08-26。本文是
languages/c/主题入门层第 35 篇。指针不只是"存地址",还能做算术——加一减一、两个指针相减、比较大小。规则不多,但每条都精确,错一步就是越界。
本文要回答的问题
p + 1到底加了几个字节?- 两个指针相减得到什么?
- 哪些指针运算合法、哪些是 UB?
一、加减:按元素步长走
c
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 指向 arr[0](假设地址 0x100)
p = p + 1; // 前进一个 int:地址 0x104,指向 arr[1]
p += 3; // 再前进 3 个:地址 0x110,指向 arr[4]
p--; // 后退一个:指向 arr[3]规则:p + n 的地址增量是 n × sizeof(*p)。int* 每次 +1 前进 4 字节,double* 前进 8 字节——类型在指针基础讲过,这里用起来。
指针算术只能用在一维数组内部(或"后一位"),其他都是 UB:
c
p = arr + 5; // 合法:指向最后一个元素的下一位(不能解引用)
p = arr + 100; // UB:超出数组范围
p = arr - 1; // UB:指向数组之前越界指针本身存着没事,但解引用就是越界访问。
二、指针相减:得到下标
c
int arr[5] = {10, 20, 30, 40, 50};
int *p = &arr[2];
int *q = &arr[4];
printf("%td\n", q - p); // 2:中间隔着 2 个元素两个指针相减(同一数组内)得到"元素个数差",类型是 ptrdiff_t(打印用 %td)。经典用途——找字符位置:
c
char s[] = "hello world";
char *found = strchr(s, 'o');
if (found != NULL) {
printf("%ld\n", found - s); // 4:'o' 在 s 的第 4 位
}限制:两个指针必须属于同一个数组(或同一块分配的内存),否则相减是 UB。
三、指针比较:同一数组内才有意义
c
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
for (int *q = arr; q < arr + 5; q++) { // q < 结尾:标准遍历
printf("%d ", *q);
}<、>、<=、>= 对指针有效,前提还是"同一数组内"。==/!= 则任何指针都行(比"是否指向同一地址")。NULL 比较也是 ==:
c
if (p == NULL) { ... }四、void* 不能算术
c
void *p = malloc(100);
p + 1; // 编译错误!void* 没有"元素大小"void* 是"指向未知类型"的指针,编译器不知道步长,不能做指针算术(GCC 会扩展当成 1 字节,但非标准)。要做算术先转成 char*(1 字节步长)或具体类型。
五、指针运算 vs 下标:选哪个
c
// 两者等价
arr[i] == *(arr + i)
&arr[i] == arr + i| 场景 | 推荐 |
|---|---|
| 随机访问(arr[k]) | 下标 |
| 顺序扫描(遍历一遍) | 指针推进(q++)顺手 |
| 在函数里操作数组参数 | 两者皆可,跟团队风格 |
现代编译器两者生成同样的代码,读起来舒服优先。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 越界指针解引用 | UB/段错误 | 边界内运算 |
| 不同数组的指针相减 | 无意义值 | 只对同数组做 |
| 忘了步长是元素大小 | 地址不对 | 记住 n × sizeof |
void* 做算术 | 编译错误 | 先转 char* |
相减结果用 %d 打印 | 类型不匹配 | 用 %td |
七、与本站主线衔接
- 指针算术在汇编里就是
lea/imul算偏移,见与汇编、性能剖析衔接; ptrdiff_t与无符号size_t的对比,见有符号与无符号;- 缓冲区操作(memcpy 等)里的指针算术,见内存与字节操作。
一句话总结
指针加减按元素步长走(n × sizeof)、同数组内相减得到下标(%td)、同数组内才能比较;越界指针可存不可解引用、void* 不能算术——五条规则,指针运算全在这里。