• @zw963 , 怎么能看出来,我就是说明 alias 是引用计数?

    参见我的第一个回复,entry1->var = entry2->var;重点就是这句, 对于每一个全局变量,ruby vm 会产生一个 global_variable 实例,

    对于 $a = 1 ruby vm 会产生一个新的 global_variable 实例, 并且 $a 对应产生一个 global_entry 实例,这个 global_entry 中的 var 指向这个 global_variable 实例。

    对于 alias $b, $a, ruby vm 执行了 entry1->var = entry2->var 之后 $b 对应的 global_entry 的 var 就指向了 $a 对应的 global_entry 的 var. 也就是两者都指向了相同的 global_variable 实例,

    即说白了, $b, $a 都引用同一份 global_variable 实例, 这一点,大家也都说过了,我只是想用源码来证明这一点的存在。

    关键源码,列出来了,大家都是搞程序的,难道非要说的一清二白吗? 这一问题,我已尽力了,不在多说了,就这样,谢谢观看!

  • @Anleb , 不好意思,我说过了,不太会画图,所以只能这样了,其实我觉得你如果细看那段 c 的代码,应该会很清楚的。

    侯捷老师说过,源码之前,了无秘密。

    如果想弄得透一些,这点 c 代码,都懒得分析的话,我就没什么话说了。

  • @skandhas , 嘿嘿,俺不擅长画图啊,画不好,感觉 lz 属于打破砂锅的类型,还是觉得代码最直接。

  • @Anleb

    直接把代码贴过来:

    void
    rb_alias_variable(ID name1, ID name2)
    {
        struct global_entry *entry1, *entry2;
        st_data_t data1;
    
        if (rb_safe_level() >= 4)
        rb_raise(rb_eSecurityError, "Insecure: can't alias global variable");
    
        entry2 = rb_global_entry(name2);
        if (!st_lookup(rb_global_tbl, (st_data_t)name1, &data1)) {
        entry1 = ALLOC(struct global_entry);
        entry1->id = name1;
        st_add_direct(rb_global_tbl, name1, (st_data_t)entry1);
        }
        else if ((entry1 = (struct global_entry *)data1)->var != entry2->var) {
        struct global_variable *var = entry1->var;
        if (var->block_trace) {
            rb_raise(rb_eRuntimeError, "can't alias in tracer");
        }
        var->counter--;
        if (var->counter == 0) {
            struct trace_var *trace = var->trace;
            while (trace) {
            struct trace_var *next = trace->next;
            xfree(trace);
            trace = next;
            }
            xfree(var);
        }
        }
        else {
        return;
        }
        entry2->var->counter++;
        entry1->var = entry2->var;
    }
    

    对于 alias $b, $a, 对应的是调用这里的 rb_alias_variable(:"$b", :"$a") 而 entry2->var 是 $a 对应的全局变量实体,它的类型为

    struct global_variable {
        int   counter;
        void *data;
        gvar_getter_t *getter;
        gvar_setter_t *setter;
        gvar_marker_t *marker;
        int block_trace;
        struct trace_var *trace;
    };
    

    代码中的 entry1->var = entry2->var; 就是把 $b 的 var 指向 $a 的 var, 同时 $a 的 var 会增加它的 alias 引用计数。

    另外,当给全局变量赋值时,会调用 rb_gvar_set, rb_gvar_set 会调用 val_setter.

    
    VALUE
    rb_gvar_set(struct global_entry *entry, VALUE val)
    {
        struct trace_data trace;
        struct global_variable *var = entry->var;
    
        if (rb_safe_level() >= 4)
        rb_raise(rb_eSecurityError, "Insecure: can't change global variable value");
        (*var->setter)(val, entry->id, var->data, var);
    
        if (var->trace && !var->block_trace) {
        var->block_trace = 1;
        trace.trace = var->trace;
        trace.val = val;
        rb_ensure(trace_ev, (VALUE)&trace, trace_en, (VALUE)var);
        }
        return val;
    }
    
    void
    val_setter(VALUE val, ID id, void *data, struct global_variable *var)
    {
        var->data = (void*)val;
    }
    

    当获取全局变量时,会调用 rb_gvar_get, rb_gvar_get 会调用 val_getter.

    
    VALUE
    rb_gvar_get(struct global_entry *entry)
    {
        struct global_variable *var = entry->var;
        return (*var->getter)(entry->id, var->data, var);
    }
    
    VALUE
    val_getter(ID id, void *data, struct global_variable *var)
    {
        return (VALUE)data;
    }
    
    

    仔细看看这里的代码,这就不再多说了, 你应该能明白原因了。

  • @Anleb

    1. alias :$b :$a 这个是不符合语法的.

    alias 一个用法是可以 alias 方法,在一个用法是可以 alias 全局变量。

    参见镐头书 Aliasing

    alias new_name old_name

    This creates a new name that refers to an existing method, operator, global variable, or regular expression backreference ($&, $`, $', and $+). Local variables, instance variables, class variables, and constants may not be aliased. The parameters to alias may be names or symbols.

    如果你用 alias :$b :$a, symbol 这种形式 ruby 会认为你要 alias 一个 method,当然是找不到了, 就出现了 undefined method $a' for classObject' (NameError)

    2. 对于给方法 alias,ruby 调用的是 rb_alias, 对于给全局变量 alias, ruby 调用的是 rb_alias_variable.

    对于 rb_alias, 当一个方法被 alias 的,如果稍后在重定义它,那么 ruby 会保留原有的定义, 如果 没有被 alias,原有的定义被丢掉。

    这即是你看到的 "修改 ask 或者 old_ask 不会相互影响"的效果.

    如下可以证明.

    def ask
        p 11111
    end 
    
    def ask
        p 22222 
    end
    

    执行 ruby -d test.rb, 会看到输出 test.rb:7: warning: method redefined; discarding old ask。

    如果

    def ask
        p 11111
    end 
    
    alias :old_ask :ask
    
    def ask
        p 22222 
    end
    

    执行 ruby -d test.rb, 则没有任何输出。

    对于 rb_alias_variable, ruby 是直接把 2 个全局变量都指向了一份变量实体,要证明这一点,只能看 它的代码,参见 rb_alias_variabe 的代码,其中最后一句 entry1->var = entry2->var;

  • The bastards book of Ruby at 2012年05月17日

    多谢分享,相当不错。

  • @junlai , 多谢,学习了!

  • Topic.find(@user.favorite_topic_ids).sort_by { |topic| @user.favorite_topic_ids.index(topic.id) }
    
    
    
  • define_method :hello do keys += [:hello,:bye] end

    这个代码块创建了一个 proc 对象并创建了一个 define_keys 方法的 binding,关联到 这个 proc 对象,也就是闭包了,不知道咋描述。

    而这个 keys, 可以认为是这个 proc 对象,有个引用,初始为 keys, 每次调用 a.hello, 都会调用 这个 proc 对象,并更新那个 keys 的引用,所以会出现这样的结果。

    就跟如下的代码,效果一样.

    module M
      def define_keys(*keys)
        local_keys = keys
        define_method :hello do 
            local_keys += [:hello,:bye]
        end
      end
    end
    
    
    
  • 求助多个 Hash 操作问题 at 2012年04月22日

    @mobiwolf 没用过, Nokogiri,不过可以试试 send, 直接写成:

    builder.send(:"joy_button_#{f}", "value" =>"1")