在Rust中,比较分配的指针是否定义良好?

12

我有一个指针buf:*const T,指向一个分配了n个元素的T的起始位置,并定义以下检查:

let in_alloc = buf <= ptr && ptr < unsafe { buf.add(n) };

你指出in_alloc是否保证对于位于buf分配范围内的任何ptr都为true,并且在任何其他情况下都为false? 我们可以假设 ptr 是一个有效的指向T对象的指针(因此不会是错位/空/悬挂),但它可能是来自与buf不同的分配。 最后,我们可以假设T不为零大小。
3个回答

8

回答标题,比较任意两个指针是明确定义的,因为指针实现了Ord

由于指针被完全排序,问题的主体可以轻松地从此中得出。你有一组不同的指针,从buf + 0开始,以buf + (n - 1)结束。如果ptr小于buf,它不能等于它们中的任何一个。如果ptr大于buf + (n - 1),它也不能等于它们中的任何一个。如果ptr是其中之一,那么这两个表达式都计算为真。

你可以使用Range来绕过一些问题:

let end = unsafe { buf.add(n) };
let in_alloc = (buf..end).contains(ptr);

这通常被用来检查一个片段是否包含指针,例如这里


2
我喜欢这个答案,因为它仅取决于指针类型的特征实现所暗示的数学属性(以及知道分配不能以某种方式“交错”)。 - Kevin Reid
指针被完全排序后,问题的主体就很容易跟进了。知道指针是完全排序的足够吗?什么保证总序与算术一致?在C++中,std::less为指针提供了完全排序,但这还不足以实现检查某个指针是否属于已分配的块。 - Language Lawyer
@LanguageLawyer 问题的假设是这些指针对于 T 是对齐的。这就是 Kevin 上面提到的交错的原因。对于指向 T 的对齐指针的总序列不允许重叠范围。(与非对齐指针不同,编译器可以按照明显的方式对它们进行排序,但严格限制在 buf + 0buf + 1 之间,因为它想成为一个垃圾编译器。) - GManNickG
对于指向 T 类型的对齐指针的总序列,不允许重叠范围。这里的“对齐指针”是什么意思,以保证这一点? - Language Lawyer
@LanguageLawyer 实际上,我认为需要对齐,但即使不需要也可以。具体来说,Range<*const T> 将排序与算术相关联。这在文档示例中明确利用,例如切片 as_ptr_range。更普遍地说,Rust 指针(主要是由于 LLVM)实现了类似整数的语义(有时过于热衷于优化)。UCG WG 中有一些关于使其更加正式的讨论(尽管目前已经停滞不前)。 - GManNickG
我认为重要的是要记住 Rust 没有正式的规范。它的语义是由文档、LLVM 的行为、它可能想要做的事情以及人们对它的期望混合定义的。整数般的特性就是一个例子 - 人们(包括语言实现)将使用 (x..y).contains(p) 来检查切片成员资格,或者 p.wrapping_add 后跟比较来检查包装。当正式规范最终出现时,它需要提供显式而不是隐式的连接。我会编辑答案并提供一些示例。 - GManNickG

1
根据官方文档,可以生成一个指向对象分配的下一个字节(即+1)的裸指针,但是不能对该指针进行解引用操作,但可以用于比较,例如在循环中进行边界检查。除此之外,由于行为未定义,所以您无法得到任何保证。因此,在您的情况下,出于这个原因,向指针添加任意偏移量都不是一个好主意。因此,更具体地回答,只要buf.add(n)指向的地址最多比buf指向的对象的分配多1个字节,就会返回您期望的值。有关详细信息,请参见https://doc.rust-lang.org/1.59.0/std/primitive.pointer.html#method.offset

这并没有回答问题。 - orlp
答案是否定的,因为这是未定义行为,无法保证。 - Diego Veralli
2
我的问题的第一句话说明了buf指向了一个大小为n个元素的T类型的分配内存。因此,buf.add(n)是已定义的行为。 - orlp
你说得完全正确,我漏掉了那一部分。 - Diego Veralli

0

是的,这段代码按照您的期望工作。比较指向不同分配的指针应该像std::ptr::eq的文档所暗示的那样按预期行为。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接