std::midpoint(ptr1, ptr2) 是唯一标准强制规避 ptr2 - ptr1 溢出的指针中点计算方式;手写 ptr1 + (ptr2 - ptr1) / 2 因 ptrdiff_t 减法溢出导致未定义行为,而 std::midpoint 通过缩放偏移绕过该问题。

std::midpoint(ptr1, ptr2) 是目前 C++ 中唯一被标准强制要求规避 ptr2 - ptr1 溢出风险的指针中点计算方式;手写 ptr1 + (ptr2 - ptr1) / 2 在超大数组下可能触发未定义行为,不能当作等价替代。
为什么 ptr1 + (ptr2 - ptr1) / 2 在指针算术中会崩
问题不在除法,而在减法:ptr2 - ptr1 返回 ptrdiff_t,其取值范围远小于地址跨度。例如在 64 位系统上,ptrdiff_t 通常是带符号 64 位整数(最大约 9.2e18),但某些巨型映射或 malloc 分配的缓冲区地址差可能接近 UINTPTR_MAX(1.8e19)。此时 ptr2 - ptr1 溢出,整个表达式变成未定义行为——不是“结果错”,而是编译器可随意优化、静默截断甚至删掉逻辑。
-
std::midpoint要求实现绕过该减法峰值,等价于先缩放偏移再叠加,不依赖ptrdiff_t能容纳全程距离 - 它不要求你手动检查
ptr2 >= ptr1,但若违反“同数组”前提(如两块malloc内存),行为仍为未定义——编译器不报错,运行时可能返回非法地址 -
nullptr只允许成对出现:std::midpoint(nullptr, nullptr)合法;std::midpoint(nullptr, p)不合法
std::midpoint 对指针类型和内存布局的硬性约束
它不是“地址平均器”,而是严格面向标准定义的指针算术:两个参数必须是同一类型的原生指针,且指向同一对象(或数组)及其 one-past-the-end 位置。
- 不接受
void*、const T*与T*混用(即使能隐式转换,模板实例化也会失败) -
std::vector::iterator、std::string::iterator等类指针类型直接传入会编译失败;必须显式转为原生指针:std::midpoint(&v[0], &v[0] + v.size())或std::midpoint(std::data(v), std::data(v) + v.size()) - C 风格数组可用
std::midpoint(arr, arr + N),但N必须 ≤ 数组长度,否则arr + N超出one-past-the-end就未定义 - 结构体成员地址(如
&s.a和&s.b)不能用——它们不是同一数组元素,标准未定义此类行为
二分查找中用 std::midpoint 的典型安全模式
它只解决中点计算这一步的安全性,其余逻辑(空区间判断、有效性校验、迭代终止)仍需你负责。
- 必须提前检查指针有效性:
if (first == last) return not_found;,否则对空范围调用std::midpoint(first, last)虽合法(因last是one-past-the-end),但后续解引用会崩溃 - 起始指针不能为
nullptr;结束指针必须 ≥ 起始指针(或为one-past-the-end),否则结果虽定义良好,但语义上可能指向无效区域 - 示例安全写法:
int arr[1000]; int* first = arr; int* last = arr + 1000; // valid one-past-the-end while (first
容易被忽略的边界事实:它不管业务逻辑,只保算术正确
std::midpoint 不做任何运行时检查:不验证指针是否悬空、是否越界、是否属于同一分配块;也不保证结果指针能安全解引用。它只承诺——在你满足所有静态前提(同类型、同数组、C++20、)的前提下,算出来的地址值数学等价且不会因中间减法溢出而失效。
- 最常漏掉的是头文件:
#include,不是或 - 编译器必须启用 C++20:
-std=c++20(GCC/Clang)或/std:c++20(MSVC ≥ 19.28) - 它返回的指针类型与输入完全一致,不会自动加
const或降级;若你需要const int*,输入也得是const int*
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











