std::midpoint(ptr1, ptr2)比ptr1 + (ptr2 - ptr1) / 2更安全,因后者中ptr2 - ptr1结果类型ptrdiff_t可能溢出,而std::midpoint标准强制规避该问题,且要求指针同类型、同数组,不校验业务有效性。

std::midpoint(ptr1, ptr2) 为什么比 ptr1 + (ptr2 - ptr1) / 2 更安全
因为 ptr2 - ptr1 的结果类型是 ptrdiff_t,而它的取值范围可能小于指针能表示的地址跨度。当数组极大(比如接近 SIZE_MAX)时,ptr2 - ptr1 可能溢出,触发未定义行为;std::midpoint 明确要求实现规避该问题,等价于先做无溢出差值缩放,再偏移。
它不依赖 operator- 返回值可容纳整个距离——这是标准强制保证,不是编译器“尽力而为”。
- 必须传入同类型的指针(
int*和const int*不行,void*直接编译失败) - 两个指针必须指向同一数组(或 one-past-the-end),否则行为未定义
-
std::midpoint(nullptr, nullptr)是合法的,返回nullptr;但std::midpoint(nullptr, p)未定义
std::midpoint 计算指针中点时的边界行为
它不检查区间有效性,只确保算术安全。例如:
int arr[3] = {1, 2, 3};
int* begin = &arr[0];
int* end = &arr[3]; // one-past-the-end
auto mid = std::midpoint(begin, end); // 合法,指向 &arr[1](即 arr[1] 的地址)
但如果 begin > end,结果仍是定义良好的(比如 std::midpoint(&arr[2], &arr[0]) 返回 &arr[1]),只是语义上可能不符合你的业务逻辑预期。
立即学习“C++免费学习笔记(深入)”;
- 结果指针一定可表示为
T*,且满足mid == begin + (end - begin) / 2的数学含义(在无溢出前提下) - 不保证
mid指向有效对象——你仍需确保该地址在数组范围内,std::midpoint不做运行时校验 - 对
char*、uint8_t*等小粒度指针同样适用,且安全优势更明显(因地址跨度更容易触达ptrdiff_t上限)
常见误用:什么时候不该用 std::midpoint 计算指针中点
它不是通用“地址平均”工具。以下情况应避免使用:
- 指针来自不同内存块(如堆上两块
malloc的内存),即使类型相同 —— 违反“同一数组”前提,行为未定义 - 用
std::midpoint处理uintptr_t或整数地址(如0x1000和0x2000)—— 它不接受整数,必须是真正指针类型 - 期望结果自动做边界 clamp(比如防止
mid超出[begin, end))—— 它不做任何逻辑约束,越界仍会算出一个合法指针值 - 在 offsetof 或结构体内存布局计算中混用——
std::midpoint对非数组对象的指针无意义,标准未覆盖此类场景
C++20 之前如何安全模拟指针 midpoint
老标准下不能用 std::midpoint,但也不能直接写 p + (q - p) / 2 —— 因为 q - p 本身可能溢出。正确做法是依赖指针减法的定义域特性:
template <typename T>
constexpr T* safe_midpoint(T* a, T* b) {
auto diff = reinterpret_cast<uintptr_t>(b) - reinterpret_cast<uintptr_t>(a);
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(a) + diff / 2);
}
这个实现绕开了 ptrdiff_t,用 uintptr_t 承载地址差(前提是平台支持该类型且足够宽)。但它丧失了标准保证的“同一数组”语义检查,需由调用者严格保证输入合法性。
真正容易被忽略的是:std::midpoint 的安全性只作用于算术过程本身;它不延伸到解引用、越界访问、生命周期管理这些后续步骤——那些依旧得靠你自己的逻辑兜底。


















