在尝试使用Vala并检查生成的C源代码后,我想出了以下Vala代码:
class Foo : GLib.Object {
public string baz;
}
class Main : GLib.Object {
public static Foo foo;
public static void bar(Foo f) {
foo = null;
f.baz = "Hi";
}
public static int main(string[] args) {
foo = new Foo();
bar(foo);
return 0;
}
}
检查生成的C代码我意识到Vala编译器在将 foo 传递给 bar 时没有插入引用计数(RC)增量。所以据我所知, bar 中的第一行会将 foo 的RC减少为0,这反过来会释放 foo ,有效地使传递的变量 f 成为悬挂指针,然后在 bar 的第二行访问该指针。但是,程序执行没有问题,所以我不确定我是否在这里遗漏了一些东西,或者它只是纯粹的巧合。 这里生成的C代码供参考:
void main_bar (Foo* f) {
Foo* _tmp0_;
gchar* _tmp1_;
g_return_if_fail (f != NULL);
_g_object_unref0 (main_foo);
main_foo = NULL;
_tmp0_ = f;
_tmp1_ = g_strdup ("Hi");
_g_free0 (_tmp0_->baz);
_tmp0_->baz = _tmp1_;
}
gint main_main (gchar** args, int args_length1) {
gint result = 0;
Foo* _tmp0_;
Foo* _tmp1_;
_tmp0_ = foo_new ();
_g_object_unref0 (main_foo);
main_foo = _tmp0_;
_tmp1_ = main_foo;
main_bar (_tmp1_);
result = 0;
return result;
}
答案 0 :(得分:2)
这是正确的行为。仅计算owned
个引用。参数为unowned
,除非另有明确说明。因此,f
中的bar
永远不会被引用计数,因为调用者负责维护引用计数。变量存储位置(类字段,堆栈变量,全局变量)都是owned
。
所以,让我们分别检查main
和bar
:
main
创建Foo
的实例,需要将其放在某处。它将它放在拥有它的全局变量foo
中。现在,通过foo
创建的对象有一个引用。然后,我们调用bar
,其中包含参数foo
。我们知道foo
已经引用了该对象,并且将它作为参数传递不需要我们增加引用,除非参数是owned
。因此,我们只需将指针foo
传递给bar
。
bar
采用名为Foo
的{{1}}类型的参数,该参数不属于此类。它为一个名为f
的完全不相关的全局变量赋值null,这会减少对象的引用计数,并在必要时对其进行清理。然后,它会对foo
中的字段进行分配。
要使“正确”工作,编译器必须1)理解f
和foo
是相同的,即使您可以使用任何参数调用f
,2)知道在bar
上递减引用计数与在某些情况下将其递减到零略有不同。对于任何无法解决暂停问题的编译器来说,这太复杂了。
要使代码按预期工作,您有两种选择:
将新对象分配给全局变量foo
和传递给foo
的堆栈变量。您现在已通过调用bar
确保变量保持有效。
让bar
取代bar
而不是owned Foo f
。这将导致调用者在传递Foo f
之前递增引用,并且foo
在引用完成时递减引用。
简而言之,调用者有责任确保变量在传递给该方法的生命周期的方法时仍然存在。您可以想象,当该方法为bar
时会变得更复杂。