Ruby alias 有问题,你了解多少 (不好意思,公司没办法上外网,有解了)

anleb · 2012年06月13日 · 最后由 1010101001110 回复于 2016年06月03日 · 4986 次阅读
def ask
    p 11111
end 

alias :old_ask :ask

def ask
    p 22222 
end

#修改ask或者old_ask不会相互影响

$a=1

alias $b $a

$b=2

#修改 全局变量a或者b都会影响对方,为什么?

我在 ruby 1.9.3 和 1.8.7 环境中 都无法运行以上代码 报错如下 test.rb:15: undefined method $a' for classObject' (NameError)

我这里执行代码有问题。NameError: undefined method $a' for classObject'

我很好奇,就算没有问题,楼主为什么要给一个变量 alias

楼主的代码写的有些问题吧,应去掉那两个冒号~

$a=1
alias $b $a
$b=2

p $a.object_id 
p $b.object_id 

你会发现$a和$b引用的是同一个对象。

#3 楼 @skandhas #2 楼 @googya #1 楼 @ery

代码肯定没问题,你们仔细看,到底里面 是怎么回事?

匿名 #5 2012年06月14日

同 1L,2L test.rb:15: undefined method $a' for classObject' (NameError)

虽然代码有点问题,但是意思我明白了, 也许我的回答不严谨,我估计原因如下:

第一个问题,修改 ask 或者 old_ask 不会相互影响,为什么?

因为,在 ruby 中你无法修改函数的内容, 你只能重新定义一个新的函数。 也许我对 ruby 的高级编程了解的不够, 也许可以修改函数的内容,而我不知道, 但是,我相信,alias,语法, 只是给函数起了一个别名而已, 并没有修改函数体的内容。

def ask
    p 11111
end 
alias :old_ask :ask

以上代码,导致 old_ask ask 两个函数名指向同一个函数对象

def ask
    p 22222 
end

以上代码,定义了一个新的函数,名字是 ask 可以这么理解,以前 ask 指向函数 1,现在 ask 指向函数 2 但是 old_ask 依然指向函数 1

第二个问题,修改 全局变量 a 或者 b 都会影响对方,为什么?

$a=1
alias $b $a

以上代码,导致变量$b $a同时指向一个对象 (该对象的内容目前是'1') 因此,$b $a其实是一个对象, 所以 此刻,$b=2 等效于 $a=2

说的有点多,

我觉得如果你了解 C++ 的指针,以及 C++ 的函数指针, 那么,你就能轻易的理解这个问题。 这个问题和指针很像。

#4 楼 @Anleb

$a=1

alias :$b :$a

$b=2

这段代码没问题吗?你试了吗? $b和$a前的冒号是不是应该去掉?

@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;

#7 楼 @skandhas 对 冒号要去掉,

#6 楼 @ery

你的答案是正确的,很感谢你的回答,第一个 方法的 alias 没有问题,我也是这么理解的,我说下我的看法: ‘’‘ $a=1 p $a.object_id

alias $b $a

p $b.object_id

$b=2

p $b.object_id

p $a.object_id ’‘’ 输出:3 3 5 5

但是 ruby 的 整型都是 用了 类似了 Java c#的驻留技术,如,a=1,b=1,都是同一个object_id,思考下

$b指针 指向了 2,为什么会影响到$a,难道 $b是引用传值,把 1 修改成 2 吗?

#8 楼 @wx1452

方法的 alias 我们都理解了,麻烦你 说一下 变量的好吗?你的意思是说,全局变量的重命名是 实体指向 变量,而不是 变量指向 实体 吗?

@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;
}

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

#12 楼 @wx1452 贴这么一堆 C 码,不如给他画张图理解的容易。

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

#12 楼 @wx1452 朋友来张图,大家都等着答案呢

楼主的意思我大概明白了。

首先你要明白:alias 创建别名,其实创建的是引用。无论是任何类型对象,其表现都是一致的。

你的代码的问题不是出在 alias, 而是出在方法的定义以及全局变量定义.

def ask # => 这是定义了一个新的方法.(即 ask 指向了一个新的地址) p 22222
end

$b = 2 # => 因为之前已经存在全局变量$b, 这里直接修改$b指向的那个地址的值。

以上回答全都是凭经验猜测,看到楼上 @wx1452 @skandhas 指出的那一堆 C 源码,我就崩溃,直接无视了.(因为我看也看不懂), 不知道我理解的对不对?

#16 楼 @zw963 这些 我上面都分析了,问题是 为什么 $b变了 会影响到 $a

#17 楼 @Anleb

不影响,干嘛叫全局变量?? 之前有个$b, 你又新整了个$b, 因为是全局作用域,当然是同一个。

#18 楼 @zw963 我是说,$b改变会影响$a

#19 楼 @Anleb

因为你改变了$b, 所以跟着改变了$a, 引用引用~~ 我觉得我说的够清楚了吧...

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

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

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

#21 楼 @wx1452

不知道我 16 楼的理解对不对?

请不要说然我看 C 源码... 看了半天我没明白讲啥。你难道只是为了说明 alias 是引用计数?

@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 实例,这一点,大家也都说过了,我只是想用源码来证明这一点的存在。

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

对一个变量进行 alias,没见过这样用的。

#23 楼 @wx1452

呵呵,你这一番表述,我看明白了。(和我理解的完全一样的,只不过我是想出来的,你是翻源码翻出来的,你是站立在源码的视角,有论证,我是站在对 alias 以及全局变量角度去臆想...)

其实我只是问你个答案,是不是我所理解得那样... 你说是就可以了。

#24 楼 @sevk

alias 是 Ruby 内置的特殊关键字。是针对任意对象操作的。虽然没见过,不代表不可以。

#10 楼 @Anleb 又是指针的问题 C++ 中有一个东西叫做 指针的指针 这个问题可以这么理解, 以下都是我的猜测

$a=1
# 创建了2个指针$a和x,
# $a指向x
# x指向1

alias $b $a
# 创建了1个指针$b
# $b指向x

$b=2
# x指向2

为什么是这么样子?

我只能说,ruby 就是这么设计的。

为什么这么设计?

我只能说,设计师有设计师的道理。

说来说去还是指针的问题。 我觉得楼主,如果对这类问题这么感兴趣的话, 可以研究一下 C++ 或者 C, 因为指针是 C 的概念, 或者读读 ruby 解释器的源码。

这个问题的确有点绕, 但是我觉得, ruby 在 alias 这个问题的设计上,挺合理的。

#26 楼 @ery Good 我也是这么思考的,我是这么认为的,a b 是双向 指针

a <---->b 然后 -=====》指向实例,和你的优点接近了

#27 楼 @Anleb 我不明白什么是双向指针, 但是我认为, a 指向 x b 指向 x 除此之外 a 和 b 没有任何关系 a 不指向 b b 也不指向 a

不允许上外网,。。。这个是内网?

#28 楼 @ery 明白了

#29 楼 @hhuai 我都是下班回家 和大家一起交流

#30 楼 @Anleb 什么破公司,居然不允许上外网

需要 登录 后方可回复, 如果你还没有账号请 注册新账号