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 class
Object' (NameError)
我这里执行代码有问题。NameError: undefined method $a' for class
Object'
我很好奇,就算没有问题,楼主为什么要给一个变量 alias?
楼主的代码写的有些问题吧,应去掉那两个冒号~
$a=1
alias $b $a
$b=2
p $a.object_id
p $b.object_id
你会发现$a和$b引用的是同一个对象。
虽然代码有点问题,但是意思我明白了, 也许我的回答不严谨,我估计原因如下:
因为,在 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=1
alias $b $a
以上代码,导致变量$b $a同时指向一个对象 (该对象的内容目前是'1')
因此,$b $a其实是一个对象,
所以 此刻,$b=2
等效于 $a=2
我觉得如果你了解 C++ 的指针,以及 C++ 的函数指针, 那么,你就能轻易的理解这个问题。 这个问题和指针很像。
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;
你的答案是正确的,很感谢你的回答,第一个 方法的 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 吗?
直接把代码贴过来:
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 , 不好意思,我说过了,不太会画图,所以只能这样了,其实我觉得你如果细看那段 c 的代码,应该会很清楚的。
侯捷老师说过,源码之前,了无秘密。
如果想弄得透一些,这点 c 代码,都懒得分析的话,我就没什么话说了。
@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 实例,这一点,大家也都说过了,我只是想用源码来证明这一点的存在。
关键源码,列出来了,大家都是搞程序的,难道非要说的一清二白吗? 这一问题,我已尽力了,不在多说了,就这样,谢谢观看!
#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 这个问题的设计上,挺合理的。