在尝试使用 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;
}
最佳答案
这是正确的行为。仅计算 owned
引用。参数是 unowned
,除非另有明确说明。因此 bar
中的 f
永远不会被引用计数,因为调用者负责维护引用计数。变量存储位置(类字段、堆栈变量、全局变量)都是拥有的
。
所以,让我们分别检查 main
和 bar
:
main
创建了一个 Foo
的实例,它需要放在某个地方。它将它放在拥有它的全局变量 foo
中。现在通过 foo
创建的对象只有一个引用。然后我们调用 bar
,它带有一个参数 foo
。我们知道 foo
已经引用了该对象,并且将它作为参数传递不需要我们增加引用,除非参数是 owned
。因此,我们只需将指针 foo
传递给 bar
。
bar
接受一个名为 f
的 Foo
类型的参数,它不拥有。它将 null 分配给一个完全不相关的名为 foo
的全局变量,这会减少对象的引用计数,并在必要时将其清除。然后它对 f
中的字段进行赋值。
要“正确地”完成这项工作,编译器必须 1) 理解 foo
和 f
是相同的,即使您可以调用 bar
与任何参数,2) 知道减少 foo
上的引用计数与在某些情况下将其减少为零略有不同。对于无法解决停机问题的任何编译器来说,这实在是太复杂了。
要使您的代码按预期工作,您有两种选择:
将您的新对象分配给全局变量
foo
和您传递给bar
的堆栈变量。您现在已通过调用bar
确保变量保持事件状态。让
bar
使用owned Foo f
而不是Foo f
。这将导致调用者在传递它之前递增foo
上的引用,并在完成时递减bar
上的引用。
简而言之,调用者有责任确保变量在将变量传递给方法时在该方法的生命周期内保持事件状态。您可以想象,当该方法为 async
时,情况会变得更加复杂。
关于Vala 引用计数和参数传递,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14396588/