Glib中的Floating references

参考:

GObject – 2.0: Floating References

Core Library-GstObject_ginitiallyunowned-CSDN博客 

gobject - How to handle floating reference parameters in GLib properly? - Stack Overflow

C 库的编写 [1] - # emerge -e world

什么是浮动引用

首先说明一下什么是“引用”。在很多程序语言设计中,内存的管理都会采用引用计数的方式:即创建内存或者有新的对象指向这块内存时,就会给这个引用的内存的计数加1;反之则引用减1。当引用计数减为0时,内存就会被释放。

C++中也有“引用”这个概念,和前面讲到的“引用”并不完全一样,至少从形式上看并不一样。因此,不要将C++中的“引用”和前面的“引用”混淆。

对于使用了引用计数的内存管理,使用 _unref 作为函数后缀名要比 _free 更恰当。例如在 GLib/GTK+ 栈中提供的函数是 g_object_new()g_object_ref()g_object_unref()

对于通常的引用,创建对象(g_object_new())时,引用计数即为1,且内存为对象的创建方所拥有,一般创建方都会持有这个引用,以便在不使用时销毁(g_object_unref())。

如果有新的对象指向这块内存/增加引用(g_object_ref())时,引用计数加1; 反之,当去除这种指向(g_object_unref())时,引用计数减1。

浮动引用(Floating Referece)是一种特殊的引用,其在创建对象时,引用计数为0,即创建的对象不会被任何代码所拥有(想象内存泄漏的状态)。对于这句话的理解,我们看下面这段程序

#include <glib.h>
#include <glib-object.h>

int main(void)
{
    GObject *obj;

    obj = g_object_new(G_TYPE_INITIALLY_UNOWNED, NULL);
    g_print("1. ref_count = %u, is_floating=%u\n", obj->ref_count, g_object_is_floating (obj));

    // Critial reported only if glib is compiled with G_ENABLE_DEBUG, not test APP.
    // Seeing g_object_finalize() in gobject.c
    g_object_unref(obj);
    g_print("2. ref_count = %u\n", obj->ref_count);

    return 0;
}

上面的程序在我的服务器上运行后,输出如下:

1. ref_count = 1, is_floating=1
2. ref_count = 0

针对上述程序,有如下结论:

  • 通过g_object_new(G_TYPE_INITIALLY_UNOWNED, NULL)创建了一个浮动引用。
  • 虽然obj接收了这个浮动引用,并且引用计数(ref_count)等于1,但后面的g_object_unref(obj)实际上是有问题的。因为创建的浮动引用“不会被任何代码所拥有”,g_object_unref(obj)并不能真正释放这块堆内存。因此这个程序是有内存泄漏的。
  • 如果glib库的编译添加了选项“G_ENABLE_DEBUG”,则会有“critical”的错误打印。但我使用的glib库应该是没有添加这个选项,所以没有这个错误打印。详见下面的glib源码:

  • 上述代码中,正确的做法应该是将浮动引用变为普通引用,即使用g_object_ref_sink函数:g_object_unref(g_object_ref_sink(obj));

为什么需要浮动引用

按照glib手册中对于Floating References的描述,“浮动引用是为了使用C语言时的方便”,并且“不应该在当前的GObject代码中使用”。也就是说,浮动引用是针对C语言而引入的,在我们通常的开发中,不应或者应尽力避免使用浮动引用。

那么,浮动引用到底给使用C语言的过程中带来了什么便利呢?glib手册是这样描述的:在使用浮动引用后,“可以这样书写代码”:

container = create_container ();
container_add_child (container, create_child());

上述代码中:create_child()创建了一个对象(浮动引用),container_add_child()将这个创建的对象添加到容器中。在这段程序中,由于create_child()创建的对象是一个浮动引用,因此我们不需要去释放这个对象(即调用g_object_unref())。如果create_child()创建的对象是一个普通引用,那么这段程序应该这样去写:

Child *child;
container = create_container ();
child = create_child ();
container_add_child (container, child);
g_object_unref (child);

即:我们需要记录create_child()的对象的地址,然后在使用完后调用g_object_unref()去释放它。注意,上述结论依赖于一个重要的程序设计理念:对象在哪一层创建的,就应该在哪一层释放。也就是说,不能要求内层函数container_add_child()去释放外层创建的对象(create_child())。因此,内层函数container_add_child()通常可以有以下几种做法

  • 仅访问对象的内容,不保留这个对象。此时内层函数不需要改变传进去的child参数的引用计数,即不需要调用g_object_ref, g_object_ref_sink, g_object_unref等。
  • 需要保留这个对象。此时内层函数需要先增加引用计数,然后在保存之后,不需要调用g_object_unref减少引用计数
  • 需要将这个对象再次传递给新的内层函数使用。此时container_add_child()一般需要先增加引用计数(g_object_ref_sink/g_object_ref),然后在传递完后,调用g_object_unref减少引用计数

g_object_ref_sink,g_object_ref的区别

如果obj是一个普通引用,则g_object_ref_sink(obj)和g_object_ref(obj)是完全一样的,都是增加引用计数。如果obj是一个浮动引用,则g_object_ref_sink(obj)会去除这个浮动引用。

因此,前面的程序中,对于函数container_add_child(),如果其接收的child有可能是浮动引用时,函数内部中在增加引用计数时,一定要使用函数g_object_ref_sink,而非g_object_ref。将前面测试floating referece的小程序改造如下:

int main(void)
{
    GObject *obj;

    obj = g_object_new(G_TYPE_INITIALLY_UNOWNED, NULL);
    g_print("1. ref_count = %u, is_floating=%u\n", obj->ref_count, g_object_is_floating (obj));

    g_object_ref_sink(obj);
    g_print("2. ref_count = %u, is_floating=%u\n", obj->ref_count, g_object_is_floating (obj));

    // Critial reported only if glib is compiled with G_ENABLE_DEBUG, not test APP.
    // Seeing g_object_finalize() in gobject.c
    //g_object_unref(g_object_ref_sink(obj));
    g_object_unref(obj);
    g_print("At end, ref_count = %u\n", obj->ref_count);

    return 0;
}

运行结果如下:

1. ref_count = 1, is_floating=1
2. ref_count = 1, is_floating=0
At end, ref_count = 0

浮动引用的其它说明

处理初始化时具有浮动引用的类型的最佳实践是,通过检查GType是否继承自GInitialyUnowned,在g_object_new()返回后,立即下沉(sink)这些引用”,从而避免在实际的业务逻辑中使用到浮动引用。

GObject *res = g_object_new_with_properties (gtype,
                                             n_props,
                                             prop_names,
                                             prop_values);

// or: if (g_type_is_a (gtype, G_TYPE_INITIALLY_UNOWNED))
if (G_IS_INITIALLY_UNOWNED (res))
  g_object_ref_sink (res);

return res;

一些对象实现可能需要在某些代码部分中保存对象的浮动状态(例如GTK3中的GtkMenu),为了实现这一点,可以使用以下代码

// save floating state
gboolean was_floating = g_object_is_floating (object);
g_object_ref_sink (object);
// protected code portion

...

// restore floating state
if (was_floating)
  g_object_force_floating (object);
else
  g_object_unref (object); // release previously acquired reference

说明:

  • 在使用object之前,先用参数was_floating记录这个object是否为浮动引用
  • 然后下沉这个浮动引用,在正常的处理中使用普通引用
  • 使用完后,如果原先的object是浮动引用,再用函数g_object_force_floating将其恢复为浮动引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值