How to check a pointer is within a slice of allocated memory

I am trying to check whether a pointer being deallocated is within a slice of memory. Currently I am using ptr::addr, which I think is likely to work ok ( and it seems to work ok ), but I am not quite sure it is definitely right. Snippets of code from here:

struct Block(NonNull<[u8]>);

impl Block {
    fn new(bsize: usize) -> Self {
        let lay = Layout::array::<u8>(bsize).unwrap();
        let p = Global::allocate(&Global, lay).unwrap();
        Self(p)
    }
    fn contains(&self, addr: usize) -> bool {
        let start = self.0.as_ptr().addr();
        addr >= start && addr <= start + self.0.len()
    }
}

and

    fn deallocate(&mut self, p: NonNull<u8>, _lay: Layout) {
        let a = p.addr().get();
        if !self.cur.contains(a) && !self.overflow_contains(a) {
            println!("Bad deallocate, aborting");
            std::process::abort();
        }

Do you think this is ok?

Pointers implement Ord, so no need to convert tousize. Also they have add methods.

Range check is wrong. Classical error. It must be start <= x && x < start + len. Note the lesser-then comparison against start + len.

(I write range check always to have the bounds on the most left and right side, and the unknown in between them)

2 Likes

I was sort-of aware of that and was going to correct it. I notice Ord says comparison is by addr, so it seems to be equivalent.

The thing is though where does it say the units of an address are bytes? Maybe you could have a machine on which addresses are bits or nibbles or something. Perhaps that is just implicit and understood.

But anyway, thanks, and I agree using pointer arithmetic and comparison seems better.

The most basic unit of memory in Rust is a byte.

1 Like

The improved/corrected version is here:

Snippets:

    fn contains(&self, addr: * mut u8) -> bool {
        let n = self.0.len();
        let start : *mut u8 = self.0.as_ptr() as * mut u8;
        let end : *mut u8 = unsafe{ start.add(n) };
        addr >= start && addr < end
    }

and

    fn deallocate(&mut self, p: NonNull<u8>, _lay: Layout) {
        let a : * mut u8 = p.as_ptr();
        if !self.cur.contains(a) && !self.overflow_contains(a) {
            println!("Bad deallocate, aborting");
            std::process::abort();
        }

You should handle 0 sized "allocations" somewhere if you're not already.

I think I am now doing that with this:

        let n = lay.size();
        let bsize = self.cur.0.len();
        let avail = bsize - self.idx;
        if n > avail || avail == 0 {

That should mean a zero-size allocation doesn’t use the very end of a block.

FYI this can be simplified as:

    fn contains(&self, addr: * mut u8) -> bool {
        self.0.as_ptr_range().contains(&addr.cast_const())
    }
1 Like

That didn’t quite compile, but I have gone for this:

impl Block {
    fn new(size: usize) -> Self {
        let lay = Layout::from_size_align(size,MAX_ALIGN).unwrap();
        Self(Global::allocate(&Global, lay).unwrap())
    }
    fn contains(&self, addr: *const u8) -> bool {
        unsafe { (*self.0.as_ptr()).as_ptr_range().contains(&addr) }
    }
}

I am not quite sure what MAX_ALIGN should be. I have set it to 128, but I don’t know where that number comes from!