erlang 判断导出函数问题

原文 2014-11-01 01:27:49 发表于 CSDN,这里对以前写的文章做下收录。

erlang 本身提供一个接口 erlang:function_exported/3,可以用来检查模块是否有导出函数,但是有时候这个接口无法正常使用。
下面重现一下这个问题:

1> erlang:function_exported(crypto,start,0).
false
2> crypto:start().
ok
3> erlang:function_exported(crypto,start,0).
true

注意:例子中并不是说一定要 crypto:start() 才能使用这个函数,只是说这个函数确实存在。

问题解决

现在,来看下 erlang 对这个接口的说明:
Returns true if the module Module is loaded and contains an exported function Function/Arity; otherwise false.
Returns false for any BIF (functions implemented in C rather than in Erlang).

换句话说,如果一个模块还没有被加载,就无法使用 erlang:function_exported/3 函数判断。很多模块在 erlang 启动时都没有加载到系统,都是在使用到的时候才加载,所以这个检查导出函数的接口可能会出现错误的结果。
如果要判断模块是否有导出函数,那么,我们可以像下面这么写,就可以正常使用了。

-module(test).
-compile(export_all).

function_exported(Module, Function, Arity) ->
     case erlang:module_loaded(Module) of
          true ->
               next;
          _ ->
                code:ensure_loaded(Module)
     end,
     erlang:function_exported(Module, Function, Arity).

另外,如果模块被改成 bif 也无法判断了。不过这倒不用太过担心,我们自己写的函数都不会是 bif

问题分析

最后,讨论下 erlang:function_exported/3 为何只能检查已加载过的模块?
为了实现热更新,新旧代码替换,erlang 维护了一个全局哈希表,用于描述模块导出函数信息。那么,只要加载过的模块就会被记录信息到这个哈希表,保存这些信息也要耗费一定的内存开销,而且,哈希算法本身存在冲突的可能性,元素越多,发生冲突的可能性越大,为了解决冲突,还会引入了 bucket(哈希桶)或者链表。这样,如果要保证键值分散,就会浪费很多空间,不然又会影响查找效率。

所以,erlang 只记录了加载过的模块信息,其他等到使用到的时候再加载。
erlang:function_exported/3是一个bif函数,实现函数可以在erts\emulator\beam\bif.c找到:

BIF_RETTYPE function_exported_3(BIF_ALIST_3){
    if (is_not_atom(BIF_ARG_1) || is_not_atom(BIF_ARG_2) ||  is_not_small(BIF_ARG_3)) {
        BIF_ERROR(BIF_P, BADARG);
    }
    if (erts_find_function(BIF_ARG_1, BIF_ARG_2, signed_val(BIF_ARG_3), 
            erts_active_code_ix()) == NULL) {
        BIF_RET(am_false);
    }
    BIF_RET(am_true);
}

erts_find_function实现在erts\emulator\beam\export.c中:

/*
 * Find the export entry for a loaded function.
 * Returns a NULL pointer if the given function is not loaded, or
 * a pointer to the export entry.
 *
 * Note: This function never returns export entries for BIFs
 * or functions which are not yet loaded.  This makes it suitable
 * for use by the erlang:function_exported/3 BIF or whenever you
 * cannot depend on the error_handler.
 */
Export* 
erts_find_function(Eterm m, Eterm f, unsigned int a, ErtsCodeIndex code_ix){
    struct export_templ templ;
    struct export_entry* ee;

    ee = hash_get(&export_tables[code_ix].htable, init_template(&templ,m,f,a));
    if (ee == NULL ||    (ee->ep->addressv[code_ix] == ee->ep->code+3 &&
      ee->ep->code[3] != (BeamInstr) BeamOp(op_i_generic_breakpoint))) {
        return NULL;
    }
    return ee->ep;
}

这里,export_tables 是一个全局变量,还是在 export.c,其中,code_ix 被用来控制代码版本,有时间的话再讨论 erlang 热更新机制

static IndexTable export_tables[ERTS_NUM_CODE_IX];  /* Active not locked */

所以,erlang:function_exported/3只是去查找导出函数的哈希表,找到返回true,没有就false

发表评论

邮箱地址不会被公开。 必填项已用*标注