要在 C 函数中在堆外分配永久 OCaml 对象,需要做什么?特别是,如何创建一个在运行时看起来像 OCaml 源代码中的全局变量一样的 OCaml 值。
这是我尝试生成一个故意损坏的程序,该程序忽略将一个值注册为 GC 根。
这是驱动一切的 OCaml 源文件。
(* immortal_string.ml *)
external make_string : string -> unit = "make_string"
external get_string : unit -> string = "get_string"
let () = make_string "a"
let () = Gc.full_major ()
let () = Printf.printf "%s\n" (get_string ())
Run Code Online (Sandbox Code Playgroud)
以及C实现。可能有比用作0哨兵值和静态函数更好的方法来做到这一点,但我认为意图很明确。请注意,多次调用make_string将破坏原来存在的值,但没关系。我希望垃圾收集器回收旧值指向的内存。
// lib_immortal_string.c
#include <caml/mlvalues.h>
#include <caml/memory.h>
#include <caml/alloc.h>
value *storage(void) {
// BAD! we haven't registered this thing
// as a GC root. No clue how you do that.
static value data = 0; // sentinel, will never be valid OCaml value
if (data == 0) {
data = caml_copy_string("");
}
return &data;
}
CAMLprim value
make_string(value ml_string) {
CAMLparam1(ml_string);
*storage() = ml_string;
CAMLreturn(Val_unit);
}
CAMLprim value
get_string(value ml_unit) {
CAMLparam1(ml_unit);
CAMLreturn(*storage());
}
Run Code Online (Sandbox Code Playgroud)
我预计这个程序会出现段错误,因为没有任何明显的功能可以保持data活动storage状态。data不是全局的,也不在堆栈上。但是,该程序似乎在没有回收字符串的情况下运行。
$ ocamlopt immortal_string.ml lib_immortal_string.c
./a.out
a
Run Code Online (Sandbox Code Playgroud)
所以我的问题是,制作全局 OCaml 值/堆外事物的正确方法是什么?而且,为什么上面的程序看起来可以工作而不是崩溃?
在你的小程序中几乎没有内存的重用,所以我猜字符串“a”在垃圾回收后看起来仍然是一样的,即使它没有被任何东西引用。
如果每次都调用 GC,它只会将事物返回到有序状态。最好让 GC 正常发生,这样会运行更多可能的内存状态。
您还必须执行一些循环,以便有时间失败。
您的代码的稍微修改版本实际上对我来说确实存在段错误:
external make_string : string -> unit = "make_string"
external get_string : unit -> string = "get_string"
let () =
while true do
let a = String.make (1024 * 1024) 'a' in
make_string a;
let b = String.make (1024 * 1024) 'b' in
Printf.printf "%s %s\n" (get_string ()) b
done
Run Code Online (Sandbox Code Playgroud)
将值标记为 GC 根的方法是caml_register_global_root
caml_register_global_root(&data);
Run Code Online (Sandbox Code Playgroud)
正如您所期望的那样,如果我在下面调用它,if (data == 0) {则不会出现段错误。
OCaml 手册第 19.5 节对此进行了记录。