代码之家  ›  专栏  ›  技术社区  ›  Greg Nisbet

将ocaml值永远保存在堆外/故意泄漏ocaml值

  •  2
  • Greg Nisbet  · 技术社区  · 6 年前

    要在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 ())
    

    以及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());
    }
    

    我原以为这个程序会出错,因为没有明显的保留 data storage 活用。 数据 不是全局的,不在堆栈上。然而,这个计划 出现 在不回收字符串的情况下运行。

    $ ocamlopt immortal_string.ml lib_immortal_string.c
    ./a.out
    a
    

    所以我的问题是,什么是创建全局ocaml值/堆外值的正确方法?而且,为什么上面的程序看起来工作而不是崩溃?

    1 回复  |  直到 6 年前
        1
  •  4
  •   Jeffrey Scofield    6 年前

    实际上在你的小程序中没有内存的重用,所以我猜在垃圾收集之后字符串“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
    

    将值标记为gc根的方法是 caml_register_global_root

    caml_register_global_root(&data);
    

    正如你所料,如果我把这个叫做 if (data == 0) { 没有segfault。

    这记录在 Section 19.5 of the OCaml manual .