GtkAboutDialog中的一个陷阱

众所周知,在一个GtkAboutDialog控件中,是可以用gtk_about_dialog_set_logo来设置logo的,其函数原型如下:

?
1
2
void                gtk_about_dialog_set_logo           (GtkAboutDialog *about,
                                                          GdkPixbuf *logo);

       显而易见,logo在这里是以GdkPixbuf控件的形式插入到GtkAboutDialog控件about中的,然而这里却暗藏了一个陷阱,而这个陷阱,有可能导致严重的内存泄漏。

       我们不妨以一下一段代码为例(编译为test_about_dialog):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdlib.h>
#include <stdio.h>
#include <gtk/gtk.h>
 
void show_about_dialog(GtkWidget *button);
 
int main( int argc, char **argv)
{
     GtkWidget *window;
     GtkWidget *button;
     
     gtk_init(&argc,&argv);
 
     window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
     
     button=gtk_button_new_with_label( "Click me!" );
     gtk_container_add(GTK_CONTAINER(window),button);
     
     g_signal_connect(G_OBJECT(button), "clicked" ,G_CALLBACK(show_about_dialog),NULL);
     g_signal_connect(G_OBJECT(window), "destroy" ,G_CALLBACK(gtk_main_quit),NULL);
 
     gtk_widget_show_all(window);
     
     gtk_main();
 
     return 0;
}
 
void show_about_dialog(GtkWidget *button)
{
     GtkWidget *about_dialog;
     GdkPixbuf *logo;
     
     about_dialog=gtk_about_dialog_new();
     logo=gdk_pixbuf_new_from_file( "./test.png" ,NULL);
     gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about_dialog),logo);
     
     gtk_dialog_run(GTK_DIALOG(about_dialog));
 
     gtk_widget_destroy(about_dialog);
 
     return ;
 
}

       程序运行以后,出来一个窗体,这时我们打开系统监视器(如果你熟悉命令行的话,用top命令也可以),这时可以看到test_about_dialog运行时占用了约1.4M的内存:

       这时我们点一下"Click me"按钮,test_about_dialog占用的内存量上升到约3.9M:

       此时,我们关闭AboutDialog,然后再次点击"click me"按钮以打开AboutDialog,此时,test_about_dialog占用的内存量已经上升到了5.9M:

       多次重复这一操作,test_about_dialog占用的内存会越来越多,并且,每次关闭AboutDialog以后,内存占用量并不降低,显然,AboutDialog中出现了内存泄漏。

       而事实上,关键就在于这句代码:

?
1
gtk_widget_destroy(about_dialog);

       按照惯例,这句代码将释放about_dialog即其子控件所占用的所有资源,然而事实呢?

       我们可以看一下gtk_widget_destroy的代码:

?
1
2
3
4
5
6
7
void
gtk_widget_destroy (GtkWidget *widget)
{
   g_return_if_fail (GTK_IS_WIDGET (widget));
 
   gtk_object_destroy ((GtkObject*) widget);
}

        显然,它调用了gtk_object_destroy,而gtk_object_destroy的代码如下:

?
1
2
3
4
5
6
7
8
9
void
gtk_object_destroy (GtkObject *object)
{
   g_return_if_fail (object != NULL);
   g_return_if_fail (GTK_IS_OBJECT (object));
   
   if (!(GTK_OBJECT_FLAGS (object) & GTK_IN_DESTRUCTION))
     g_object_run_dispose (G_OBJECT (object));
}

         而g_object_run_dispose代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
void
g_object_run_dispose (GObject *object)
{
   g_return_if_fail (G_IS_OBJECT (object));
   g_return_if_fail (object->ref_count > 0);
 
   g_object_ref (object);
   TRACE (GOBJECT_OBJECT_DISPOSE(object,G_TYPE_FROM_INSTANCE(object), 0));
   G_OBJECT_GET_CLASS (object)->dispose (object);
   TRACE (GOBJECT_OBJECT_DISPOSE_END(object,G_TYPE_FROM_INSTANCE(object), 0));
   g_object_unref (object);
}

         而GtkAboutDialog的dispose函数继承自GtkDialog,而GtkDialog的dispose函数又继承自GtkWindow,而GtkWindow的dispose函数如下所示:

?
1
2
3
4
5
6
7
8
9
10
static void
gtk_window_dispose (GObject *object)
{
   GtkWindow *window = GTK_WINDOW (object);
 
   gtk_window_set_focus (window, NULL);
   gtk_window_set_default (window, NULL);
 
   G_OBJECT_CLASS (gtk_window_parent_class)->dispose (object);
}

         事实上,dispose函数将会自底向上一层层地执行下去,那究竟是哪个语句最终导致资源被释放呢?

         其实就是g_object_run_dispose中的这个语句:

?
1
g_object_unref (object);

         他将使得object的ref_count降到零,进而调用dispose函数以及destroy函数,而真正负责释放资源的,是destroy函数。由destroy函数的特点知,该函数将会自底向上一层层地执行。而实现释放其子控件占用的资源的,是GtkContainer的destroy函数:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void
gtk_container_destroy (GtkObject *object)
{
   GtkContainer *container = GTK_CONTAINER (object);
 
   if (GTK_CONTAINER_RESIZE_PENDING (container))
     _gtk_container_dequeue_resize_handler (container);
 
   if (container->focus_child)
     {
       g_object_unref (container->focus_child);
       container->focus_child = NULL;
     }
 
   /* do this before walking child widgets, to avoid
    * removing children from focus chain one by one.
    */
   if (container->has_focus_chain)
     gtk_container_unset_focus_chain (container);
 
   gtk_container_foreach (container, (GtkCallback) gtk_widget_destroy, NULL);
 
   GTK_OBJECT_CLASS (parent_class)->destroy (object);
}

         事实上,这段中真正负责销毁控件的,便是这句:

?
1
gtk_container_foreach (container, (GtkCallback) gtk_widget_destroy, NULL);

         而gtk_container_foreach代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void
gtk_container_foreach (GtkContainer *container,
                GtkCallback   callback,
                gpointer      callback_data)
{
   GtkContainerClass * class ;
   
   g_return_if_fail (GTK_IS_CONTAINER (container));
   g_return_if_fail (callback != NULL);
 
   class = GTK_CONTAINER_GET_CLASS (container);
 
   if ( class ->forall)
     class ->forall (container, FALSE, callback, callback_data);
}

     从这里我们可以看出,gtk_container_destroy将对所有子控件调用gtk_widget_destroy使得它们的ref_count都降1,如果它们的ref_count都等于1的话,显然这些子控件将被销毁——由此可见,用gtk_widget_destroy销毁一个控件及其子控件是有条件的——他们的ref_count都必须等于1,而这一般都是可以满足的——如果你不单独对某个控件使用g_object_ref的话。更重要的是,forall的代码也明确的指出,子控件指的是加入到box中的children链表中的控件(即用gtk_box_pack_start等函数加入到box中的控件)。注意:这里的forall实质上是一个纯虚函数指针,因为GtkContainer控件中的forall指针为NULL,而该指针到box控件中将会被重载:

?
1
container_class->forall = gtk_box_forall;

       而gtk_box_forall的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static void
gtk_box_forall (GtkContainer *container,
         gboolean      include_internals,
         GtkCallback   callback,
         gpointer      callback_data)
{
   GtkBox *box = GTK_BOX (container);
   GtkBoxChild *child;
   GList *children;
 
   children = box->children;
   while (children)
     {
       child = children->data;
       children = children->next;
 
       if (child->pack == GTK_PACK_START)
     (* callback) (child->widget, callback_data);
     }
 
   children = g_list_last (box->children);
   while (children)
     {
       child = children->data;
       children = children->prev;
 
       if (child->pack == GTK_PACK_END)
     (* callback) (child->widget, callback_data);
     }
}

       那为什么对GtkAboutDialog控件about调用gtk_widget_destroy并不能销毁控件logo呢?答案已经很显然了,logo并不是GtkAboutDialog的子控件!所以,你如果对GtkAboutDialog控件about调用gtk_widget_destroy的话,并不能销毁logo控件!换句话说,logo控件仍然会占用相应的内存资源,而这正是内存泄漏出现的关键!

       那为什么logo控件不是GtkAboutDialog的子控件呢?

       这是因为把logo控件“加入”到窗体,实质是把它转成GtkImage控件logo_image,并调用gtk_box_pack_start把logo_image加入到GtkAboutDialog控件的窗体中实现的。相关代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void
gtk_about_dialog_set_logo (GtkAboutDialog *about,
                            GdkPixbuf      *logo)
{
   GtkAboutDialogPrivate *priv;
 
   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
 
   priv = (GtkAboutDialogPrivate *)about->private_data;
 
   g_object_freeze_notify (G_OBJECT (about));
 
   if (gtk_image_get_storage_type (GTK_IMAGE (priv->logo_image)) == GTK_IMAGE_ICON_NAME)
     g_object_notify (G_OBJECT (about), "logo-icon-name" );
 
   if (logo != NULL)
     gtk_image_set_from_pixbuf (GTK_IMAGE (priv->logo_image), logo);
   else
     {
       GList *pixbufs = gtk_window_get_default_icon_list ();
 
       if (pixbufs != NULL)
         {
           GtkIconSet *icon_set = icon_set_new_from_pixbufs (pixbufs);
 
           gtk_image_set_from_icon_set (GTK_IMAGE (priv->logo_image),
                                        icon_set, GTK_ICON_SIZE_DIALOG);
 
           gtk_icon_set_unref (icon_set);
           g_list_free (pixbufs);
         }
     }
 
   g_object_notify (G_OBJECT (about), "logo" );
 
   g_object_thaw_notify (G_OBJECT (about));
}

         把logo_image加入到GtkAboutDialog控件中的相关代码如下:

?
1
2
priv->logo_image = gtk_image_new ();
gtk_box_pack_start (GTK_BOX (vbox), priv->logo_image, FALSE, FALSE, 0);

         从中也可以看出,logo_name才是GtkAboutDialog的子控件。因此,对GtkAboutDialog控件调用gtk_widget_destroy,是无法销毁gtk_about_dialog_set_logo中logo指针所指向的GdkPixbuf控件的。而在我们写的示例程序中,每次执行show_about_dialog函数,都会执行:

?
1
logo=gdk_pixbuf_new_from_file( "./test.png" ,NULL);

         而在此之前,logo指针所指向的资源并没有被释放,于是内存泄漏就产生了。

         为了解决这一问题,我们需要对logo控件手动调用g_object_unref,故修改后的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdlib.h>
#include <stdio.h>
#include <gtk/gtk.h>
 
void show_about_dialog(GtkWidget *button);
 
int main( int argc, char **argv)
{
     GtkWidget *window;
     GtkWidget *button;
     
     gtk_init(&argc,&argv);
 
     window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
     
     button=gtk_button_new_with_label( "Click me!" );
     gtk_container_add(GTK_CONTAINER(window),button);
     
     g_signal_connect(G_OBJECT(button), "clicked" ,G_CALLBACK(show_about_dialog),NULL);
     g_signal_connect(G_OBJECT(window), "destroy" ,G_CALLBACK(gtk_main_quit),NULL);
 
     gtk_widget_show_all(window);
     
     gtk_main();
 
     return 0;
}
 
void show_about_dialog(GtkWidget *button)
{
     GtkWidget *about_dialog;
     GdkPixbuf *logo;
     
     about_dialog=gtk_about_dialog_new();
     logo=gdk_pixbuf_new_from_file( "./test.png" ,NULL);
     gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about_dialog),logo);
     
     gtk_dialog_run(GTK_DIALOG(about_dialog));
 
     gtk_widget_destroy(about_dialog);
     g_object_unref(G_OBJECT(logo));
 
     return ;
 
}

 

转载至:http://nanjingabcdefg.is-programmer.com/categories/5997/posts
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值