指针加减的是所指类型的大小而非字节,如int加1跳4字节;仅指向完整类型的指针支持算术,void不支持;ptr+n必须在同数组内或末尾,否则为未定义行为。

指针加减到底在加减什么
它加减的是「所指类型的大小」,不是字节。比如 int* 指针加 1,实际地址跳过 sizeof(int) 字节(通常是 4),不是 1 字节。
- 错误现象:
char* p = &arr[0]; p + 1跳 1 字节,int* p = &arr[0]; p + 1跳 4 字节 —— 如果误以为都是跳 1 字节,就会越界读写 - 使用场景:遍历数组、实现自定义容器迭代器、解析二进制协议(如从
uint8_t*起按uint32_t解包) - 参数差异:只有指向「完整类型」的指针支持算术;
void*不行(GCC/Clang 会警告,C++ 标准禁止)
为什么 ptr + n 要求 ptr 指向有效数组或紧邻末尾
C++ 标准只允许指针算术在「同一数组内」或「数组末尾的下一个位置」进行。超出这个范围就是未定义行为(UB),哪怕地址看起来“合法”。
- 常见错误:
int arr[5]; int* p = arr; p += 10;—— 即使没解引用,编译器也可能优化掉后续逻辑或触发 sanitizer 报错 - 性能影响:编译器依赖此规则做别名分析和循环优化;一旦 UB,优化可能让代码行为完全不可预测
- 安全边界:用
std::span<int></int>或带长度检查的封装类替代裸指针算术,能提前捕获越界
std::vector 和 std::array 的数据指针能直接做算术吗
可以,但前提是确保内存连续且对象生命周期有效。两者都保证元素连续存储,&v[0] 或 v.data() 得到的指针可安全用于算术。
- 注意点:
std::vector在push_back等操作后可能重新分配,原有指针立即失效;std::array的指针只要数组没析构就一直有效 - 兼容性:C++17 起
std::data(v)对所有标准容器可用,但只有vector/array/原生数组等连续容器才适合算术 - 示例:
auto p = v.data(); auto last = p + v.size();是安全的;但p + v.size() + 1就越界了(即使v.size() > 0)
用 std::distance 和 std::advance 替代裸指针加减
它们本质还是调用指针算术,但提供类型安全和迭代器类别适配。对原生指针,就是包装了 ptr - other_ptr 或 ptr += n。
立即学习“C++免费学习笔记(深入)”;
- 为什么用:避免手写
end - begin时搞反顺序(结果负数);std::advance对随机访问迭代器是 O(1),对单向迭代器是 O(n),提醒你注意性能代价 - 容易踩的坑:
std::distance(a, b)要求a可达b(即a 对于指针),否则 UB;不检查是否同属一个容器 - 真实场景:写泛型算法时,优先用
std::distance而不是end - begin,这样模板能同时支持指针和std::list::iterator
最常被忽略的是:指针算术本身不检查边界,也不感知容器生命周期。哪怕你算出来的地址在进程地址空间里合法,只要越出对象边界,就是未定义行为——调试器可能不报错,但开启 -O2 或 AddressSanitizer 就立刻暴露。









