You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

延迟边界检查时使用pointer::add是否安全?如何移除编译panic

移除test函数编译后的panic条件及相关疑问解答

示例代码

pub fn test(bytes: &[u8]) -> Option<u8> {
    let mut viewer = Viewer::new(bytes);
    if let Some(b'0') = viewer.try_peek() {
        viewer.skip(1);
    }

    viewer.try_peek()
}

pub struct Viewer<'a> {
    ptr: *const u8,
    end: *const u8,
    marker: std::marker::PhantomData<&'a [u8]>,
}

impl<'a> Viewer<'a> {
    #[inline]
    pub fn new(bytes: &'a [u8]) -> Self {
        let ptr = bytes.as_ptr();
        let end = unsafe { ptr.add(bytes.len()) };

        // if ptr > end {
        //     unsafe { ::std::hint::unreachable_unchecked() };
        // }

        Self {
            ptr,
            end,
            marker: std::marker::PhantomData,
        }
    }

    #[inline]
    pub fn try_peek(&self) -> Option<u8> {
        if self.ptr == self.end {
            // or >=
            return None;
        }

        Some(unsafe { *self.ptr })
    }

    #[inline]
    pub fn skip(&mut self, n: usize) {
        //let new_ptr = unsafe { self.ptr.add(n) };
        let new_ptr = self.ptr.wrapping_add(n);

        if new_ptr > self.end {
            panic!("out of bounds")
        }

        self.ptr = new_ptr;
    }
}

一、移除panic条件的方案

要去掉skip里的panic,核心是保证指针偏移后不会越界,同时规避未定义行为(UB)。结合你的test函数场景(仅在try_peek成功时调用skip(1)),可以这样处理:

  1. 场景专属优化方案:
    因为test里只有try_peek返回Some(_)时才调用skip(1),此时self.ptr < self.end是确定的,ptr.add(1)最多等于end(完全符合pointer::add的规则:结果指针可超出末尾一个字节),所以可以直接去掉panic判断,改用安全的add:

    #[inline]
    pub fn skip(&mut self, n: usize) {
        // 仅在调用方保证n不会导致指针越界时使用
        self.ptr = unsafe { self.ptr.add(n) };
    }
    

    这个方案能保留编译器的优化能力,且不会触发UB,但要求所有skip的调用都遵守“偏移量合法”的前提。

  2. 通用安全无panic方案:
    如果要让skip支持任意合法偏移且无panic,可以用debug_assert!保留调试期检查,发布期自动消除:

    #[inline]
    pub fn skip(&mut self, n: usize) {
        let new_ptr = unsafe { self.ptr.add(n) };
        // 调试期检查越界,发布期编译为空
        debug_assert!(new_ptr <= self.end);
        self.ptr = new_ptr;
    }
    

二、pointer::add vs wrapping_add的选择与优化影响

  • 文档推荐wrapping_add的原因:当偏移量n不确定时,ptr.add(n)可能让指针超出原分配对象的合法范围(除末尾+1的位置),直接触发UB。wrapping_add会绕过边界检查,无论偏移多少都返回指针,但这会让编译器失去“指针在合法范围内”的优化依据,导致生成的代码更差。
  • 在你的场景中,skip(1)的调用前提是try_peek成功,此时ptr.add(1)的结果必然在合法范围内,所以用add完全安全,还能保留编译器的优化能力。

三、为何需要unreachable_unchecked或>=判断

  1. unreachable_unchecked的作用:
    在new函数中,ptr.add(bytes.len())理论上应该满足ptr <= end,但如果bytes.len()过大导致偏移量溢出isize范围,end可能会变成小于ptr的指针,破坏这个假设。unreachable_unchecked()是告诉编译器:ptr > end的情况永远不会发生,让编译器可以基于ptr <= end的假设做激进优化(比如简化try_peek的判断逻辑)。

  2. >=判断的必要性:
    如果没有unreachable_unchecked(),编译器必须考虑ptr > end的极端情况,此时try_peek里的self.ptr == self.end判断就无法覆盖越界场景,需要改成>=才能正确返回None。但有了unreachable_unchecked()保证ptr <= end,==判断就足够了。


四、关于测试中的UB问题

当你用add替代wrapping_add且取消unreachable_unchecked注释时,程序看似正常工作,但实际上:

  • 若bytes.len()导致ptr.add(bytes.len())溢出isize,end会变成小于ptr的指针,后续try_peek的判断会出错,skip的add操作也会触发UB。
  • UB是未定义行为,程序可能暂时正常,但随时可能崩溃、输出错误结果,或被编译器优化出逻辑混乱的代码,绝对不能依赖这种表象。

内容的提问来源于stack exchange,提问作者qRoC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 17:44:51