众所周知,在一个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