假设我们具有以下玩具二叉树结构:
datatype Tree = Leaf | Branch of Tree * Tree
fun left(Branch(l,r))= l
fun right(Branch(l,r))= r
并假设我们有一些大而昂贵的计算树
val c: Tree= …
val d: Tree= Branch(c,c)
我们可以在SML / NJ解释器中验证left(d)
和right(d)
确实指向内存中的同一位置吗?
(此问题源于处理可能包含周期的惰性流,并尝试调试备忘录是否正常工作。)
答案 0 :(得分:2)
我认为我们可以通过使用 Unsafe.cast
将两个值都转换为 word
来实现,这将指针重新解释为可以与 =
进行比较的数字。这是实现此想法的 is
函数:
infix 4 is (* = > < >= ... *)
fun op is(a: 'a, b: 'a) = (Unsafe.cast a: word) = Unsafe.cast b
注意:
(a, b)
以确保类型检查器将参数限制为相同类型,否则 is
的 typeig 将是 'a * 'b -> bool
Unsafe.cast
应用程序,以防止 SML/NJ 不得不使用 polyEqual
从而避免发出 Warning: calling polyEqual
以下示例说明了向量中的结构共享:
local
fun const a _ = a
val v = Vector.tabulate(1000, const #[1,2,3])
val v = Vector.update(v, 230, #[1,2,3]) (* same value, but new allocation *)
in
val test1 = Vector.sub(v, 0) is Vector.sub(v, 999)
val test2 = Vector.sub(v, 0) is Vector.sub(v, 230)
end
它确实按预期工作。回复回复
(* val test1 = true : bool
val test2 = false : bool *)
现在这是您问题中的树示例:
local
datatype Tree
= Leaf
| Branch of Tree * Tree
fun left (Branch(l,r)) = l
fun right (Branch(l,r)) = r
val c = Branch(Leaf,Leaf) (* imagine it being something complex *)
val d = Branch(c, c)
in
val test3 = left d is right d
end
当我们尝试时,它会正确回答:
(* val test3 = true : bool *)
我认为这回答了您的问题。下面我将讨论 word
的选择以及转换到它时可能在内部发生的事情
据我所知,SML/NJ 像许多 lisps、v8、OCaml 等一样进行指针标记。能够读取指针值,而不是误解堆对象。
我认为 word
可以很好地用于此目的;它像 int 一样立即,而 unsigned 不像它......所以它应该对应于内存地址(不要阻止我)。
似乎有一个错误*阻止您直接在 repl 中检查单词值,可能是指针标记在起作用。
* 至少编译器将其报告为那样?从 v110.99 开始
一种解决方法是立即将值转换为不同的表示形式(也许需要装箱?),例如字符串或 Word64.word
fun addrOf x = Word.toString (Unsafe.cast x)
确实,当我们尝试使用新定义的 addrOf
函数来比较地址与其字符串化值时,我们可以观察到指针标记的效果
(* We'll need these definitions onwards, might as well have them here: *)
infix 5 >> <<
val op >> = Word.>>
val op << = Word.<<
val unsafeWord = Option.valOf o Word.fromString
local
val x = SOME 31 (* dummy boxed value *)
val addr = unsafeWord (addrOf x)
in
val test4 = Unsafe.cast x = addr
val test5 = Unsafe.cast x >> 0w1 = addr >> 0w1 (* get rid of lowest bit *)
end
(* val test4 = false : bool
val test5 = true : bool *)
那么,如果标签只是 SML/NJ 中机器字的最低位,就像在许多标签指针实现中一样,那么指针应该准确地是转换值右移一次,然后再次离开。
fun addrOf x = Unsafe.cast x >> 0w1 << 0w1
我们之所以进行这种看似 nop
的转换(请记住,所有指针都是偶数)是因为它在过程中正确标记了转换字值。
如果我们先左移然后右移,标签本身会通过第一个操作找到它的值,因为强制指针变成了正确的词。这就是为什么我们先右移。位,因此不会丢失有关地址的信息,但内部正确存在立即值标记。
local
fun strAddrOf x = Word.toString (Unsafe.cast x)
fun isEven x = Word.andb (x, 0w1) = 0w0
val x = SOME 42
val ogAddr = unsafeWord (strAddrOf x) (* a known-correct conversion: no shifting takes place *)
val badAddr = Unsafe.cast x << 0w1 >> 0w1
val goodAddr = Unsafe.cast x >> 0w1 << 0w1
in
val test6 = ogAddr = badAddr
val test7 = ogAddr = goodAddr
val test8 = isEven ogAddr
end
(* val test6 = false : bool
val test7 = true : bool
val test8 = true : bool *)
addrOf
中的这种移位允许您直接获取指针值,而无需中间转换(和装箱)到 string
或 word64
。当然,此解决方案会分解为实际未装箱的值,因此最好在您的 Unsafe.boxed
定义中测试对象是否以 (addrOf
) 开头,并返回 0wx0 如果您正在使用立即数。
希望这对您有用。到目前为止,它确实对我有用!