Подсчет ссылок Vala и передача параметров


После экспериментов с 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 не вставил инкремент отсчета ссылок (RC) дляfoo при передаче его вbar . Так что, насколько я понимаю, первая строка в bar уменьшит RC foo до 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;
}
1 3

1 ответ:

Это правильное поведение. Учитываются только ссылки owned. Параметры unowned, Если явно не указано иное. Таким образом, f в bar никогда не подсчитывается ссылка, потому что вызывающий отвечает за поддержание количества ссылок. Переменные места хранения (поля классов, переменные стека, глобальные переменные) - это все owned.

Итак, давайте разберем main и bar отдельно:

main создает экземпляр Foo, который нужно куда-то поместить. Он помещает его в глобальная переменная foo, которой она принадлежит. Теперь существует единственная ссылка на объект, созданный с помощью foo. Затем мы вызываем bar, который принимает параметр foo. Мы знаем, что foo уже ссылается на объект и что передача его в качестве параметра не требует увеличения ссылки, если параметр не равен owned. Поэтому мы просто передаем указатель foo в bar.

bar принимает параметр типа Foo под названием f, которым не владеет. Он присваивает значение null совершенно не связанному объекту. глобальная переменная, называемая foo, уменьшает количество ссылок на объект, очищая его по мере необходимости. Затем он выполняет присваивание полю в f.

Чтобы иметь эту работу "правильно" компилятор должен был бы 1) понимать, что foo и f - это одно и то же, даже если вы можете вызвать bar с любым параметром, 2) знать, что уменьшение количества ссылок на foo немного отличается от уменьшения его до нуля в некоторых случаях. Это просто слишком сложно для любого компилятора. способен решить проблему остановки.

Чтобы ваш код работал должным образом, у вас есть два варианта:

  1. Назначьте новый объект как глобальной переменной foo, так и переменной стека, которую вы передаете в bar. Теперь вы гарантировали, что переменная останется живой через вызов bar.

  2. Пусть bar вместо Foo f возьмут owned Foo f. Это приведет к тому, что вызывающий объект увеличит ссылку на foo перед ее передачей и bar уменьшит ссылка, когда все будет сделано.

Короче говоря, ответственность за то, чтобы переменная оставалась живой, когда она передает ее методу в течение всего срока службы этого метода, лежит на вызывающем объекте. Вы можете себе представить, что становится немного сложнее, когда этот метод async.