GTK+ Memory Management Explained
GTK+ uses a memory management system based primarily on reference counting, provided by the GLib Object System (GObject).
Unlike languages such as Java or Python, GTK+ does not use a traditional garbage collector. Instead, objects are explicitly managed through reference counts, object ownership, and destruction.
Understanding this system is essential when developing GTK applications in C because incorrect memory management can cause:
- Memory leaks (memory that is never released).
- Invalid memory access (use-after-free).
- Double freeing (releasing the same object incorrectly).
- Crashes when widgets are destroyed.
Let’s explore how it works, with practical C examples.
1. The basic concept: reference counting
Every GObject-based GTK object maintains a reference count.
The reference count indicates how many active references keep an object alive.
There are three important operations:
g_object_ref() Increases the reference count.
g_object_unref() Decreases the reference count.
g_object_ref_sink() Acquires a normal reference, consuming a floating reference if one exists.
When the reference count reaches zero, the object is finalized and its memory is released.
Example 1: Creating and destroying a GObject
#include <glib-object.h>
int main(void)
{
GObject *obj;
/* Create an object */
obj = g_object_new(G_TYPE_OBJECT, NULL);
/* Reference count is initially 1 */
/* Increase reference count */
g_object_ref(obj);
/* Reference count is now 2 */
/* Decrease reference count */
g_object_unref(obj);
/* Reference count is now 1 */
/* Release the final reference */
g_object_unref(obj);
/* Object is now finalized */
return 0;
}
What happens?
- Object created — reference count: 1.
- Call
g_object_ref()— reference count: 2. - Call
g_object_unref()— reference count: 1. - Call
g_object_unref()again — reference count: 0. - Object finalized — memory released.
2. Floating references: an important GTK+ concept
One of the most important differences between ordinary GObjects and GTK widgets is the concept of a floating reference.
Most GTK widgets are derived from GInitiallyUnowned.
When you create a widget, its initial reference is floating rather than a normal owned reference.
For example:
GtkWidget *button;
button = gtk_button_new_with_label("Click Me");
The button has an initial floating reference.
Why is this useful?
Because GTK widgets are usually organized in a hierarchy:
GtkWindow
|
+-- GtkBox
|
+-- GtkButton
|
+-- GtkLabel
The parent widget takes ownership of its children.
Example 2: Adding a button to a container (GTK3)
GtkWidget *window;
GtkWidget *button;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
button = gtk_button_new_with_label("Click Me");
/* The container takes ownership of the floating reference */
gtk_container_add(GTK_CONTAINER(window), button);
Here is what happens:
gtk_button_new_with_label()creates the button with a floating reference.gtk_container_add()adds the button to the window.- The container sinks the floating reference.
- The parent now owns the button’s reference.
- When the parent releases its ownership, the child can be destroyed if no other references exist.
Important: You normally do not need to call g_object_unref(button)
after adding a newly created widget to a GTK3 container.
Doing so would release the reference now owned by the parent.
What if you want to keep your own reference?
Suppose you want to access the button later, independently of its parent.
You can acquire another reference:
GtkWidget *button;
button = gtk_button_new_with_label("Click Me");
/* Acquire your own reference */
g_object_ref_sink(button);
/* Add it to the container */
gtk_container_add(GTK_CONTAINER(window), button);
Now the reference ownership is:
- Your application owns one reference.
- The parent container owns another reference.
When you no longer need your own reference:
g_object_unref(button);
The button can still exist because its parent owns a reference.
3. Parent-child ownership and widget destruction
GTK uses a hierarchical widget structure.
Generally, a parent widget maintains references to its children.
When a parent is destroyed or releases its children, those references are released.
Consider:
Window
|
+-- Vertical Box
|
+-- Button
|
+-- Label
If the window and its widget hierarchy are no longer needed, GTK releases the references held by the hierarchy.
However, an important distinction exists between GTK3 and GTK4.
GTK version Widget removal and destruction
———————————– ———————————–
GTK3 Uses gtk_container_remove() and
gtk_widget_destroy() patterns.
GTK4 Uses widget unparenting and
reference management;
gtk_widget_destroy() is no longer
available.
———————————————————————–
Example 3: GTK3 widget destruction
GtkWidget *window;
GtkWidget *button;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
button = gtk_button_new_with_label("Hello");
gtk_container_add(GTK_CONTAINER(window), button);
/* Destroy the window */
gtk_widget_destroy(window);
Destroying the window also destroys its widget hierarchy.
Example 4: GTK4 widget ownership
GTK4 uses APIs such as gtk_window_set_child():
GtkWidget *window;
GtkWidget *button;
window = gtk_window_new();
button = gtk_button_new_with_label("Hello");
/* The window takes ownership of the child */
gtk_window_set_child(GTK_WINDOW(window), button);
When you remove a child from its parent using GTK4’s widget APIs, the parent releases its ownership.
If another strong reference exists, the widget remains alive.
See the GTK4 Common Questions for further details.
4. Memory management of ordinary allocated memory
Not everything in GTK is a GObject.
For ordinary dynamically allocated memory, GLib provides:
g_malloc()g_malloc0()g_strdup()g_free()
These functions are similar to the standard C functions malloc(),
calloc(), strdup(), and free().
Example 5: Allocating and freeing a string
#include <glib.h>
#include <stdio.h>
int main(void)
{
gchar *name;
/* Allocate memory */
name = g_strdup("GTK Memory Management");
printf("%s\\n", name);
/* Release allocated memory */
g_free(name);
return 0;
}
Important distinction:
g_object_unref(object);
is used for reference-counted GObjects.
g_free(memory);
is used for memory allocated with GLib allocation functions.
Never interchange these functions.
5. The difference between dispose() and finalize()
When a GObject’s reference count reaches zero, its destruction happens in two important stages.
Stage 1: dispose()
The dispose() method releases references to other objects owned by the
object.
For example, suppose a custom GTK object contains a reference to another GObject.
During disposal, it should release that reference.
Stage 2: finalize()
The finalize() method releases the remaining resources associated with
the object, such as allocated memory.
The general lifecycle is:
- Object is created.
- Object is used; references are acquired and released.
- Reference count reaches zero.
dispose()releases references to other objects.finalize()releases remaining allocated resources.- Object memory is released.
Example 6: Implementing dispose() and finalize()
Imagine a custom GObject containing a reference to another object and an allocated string.
typedef struct {
GObject parent_instance;
GObject *child;
gchar *name;
} MyObject;
The disposal and finalization methods could look like this:
static void
my_object_dispose(GObject *object)
{
MyObject *self = (MyObject *)object;
/* Release reference to child */
g_clear_object(&self->child);
G_OBJECT_CLASS(my_object_parent_class)->dispose(object);
}
static void
my_object_finalize(GObject *object)
{
MyObject *self = (MyObject *)object;
/* Release allocated string */
g_free(self->name);
G_OBJECT_CLASS(my_object_parent_class)->finalize(object);
}
Notice the use of:
g_clear_object(&self->child);
This releases the object’s reference and sets its pointer to NULL.
The distinction between disposal and finalization is important because
dispose() can be called more than once, whereas finalize() is called
only once.
6. Signal callbacks and memory management
GTK applications frequently use signals to respond to user actions.
For example:
g_signal_connect(
button,
"clicked",
G_CALLBACK(on_button_clicked),
NULL
);
A common mistake is assuming that connecting a signal automatically transfers ownership of every object involved.
It does not.
You should understand the lifetime of:
- The object emitting the signal.
- The callback function’s user data.
- Any objects referenced by the callback.
Example 7: Using signal callbacks
#include <gtk/gtk.h>
static void
on_button_clicked(GtkButton *button, gpointer data)
{
g_print("Button clicked!\\n");
}
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");
g_signal_connect(
button,
"clicked",
G_CALLBACK(on_button_clicked),
NULL
);
gtk_container_add(GTK_CONTAINER(window), button);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
Here, the button is owned by its parent container. The signal connection does not require you to manually increase the button’s reference count.
If you pass dynamically allocated user data to a signal callback,
consider using g_signal_connect_data() with a destroy notifier so that
the data is released when the signal handler is disconnected or its
emitter is finalized.
7. Memory leaks caused by circular references
Another important problem is circular references.
Imagine two objects:
Object A ------> Object B
^ |
|________________|
- Object A owns a reference to Object B.
- Object B owns a reference to Object A.
Even if the application releases its external references to both objects, their reference counts may remain above zero.
Consequently, neither object gets finalized.
This is called a reference cycle.
GObject reference counting does not automatically detect such cycles.
How can you prevent this?
Use weak references when an object needs to observe another object without keeping it alive.
For example:
g_object_add_weak_pointer(
G_OBJECT(other_object),
(gpointer *)&self->other_object
);
A weak pointer does not increase the reference count. When the
referenced object is finalized, the pointer is automatically set to
NULL.
Remember to remove the weak pointer registration if its storage is going away before the referenced object.
For more advanced cases, GObject provides GWeakRef.
8. Common memory management mistakes
Mistake Consequence Correct approach
———————– ———————– ———————–
Forgetting Memory leak Release owned
g_object_unref() references.
Calling Invalid memory access Track reference
g_object_unref() ownership.
twice
Calling g_free() on a Invalid memory Use g_object_unref().
GObject management
Calling Incorrect ownership Sink or transfer the
g_object_unref() on a handling floating reference
floating widget first.
Forgetting to free Memory leak Use g_free().
allocated strings
Creating strong Objects never finalized Use weak references or reference cycles break cycles.
Using an object after Use-after-free Ensure a valid strong finalization reference exists. ———————————————————————–
9. Best practices for GTK+ memory management
Here are some practical rules to follow when developing GTK applications:
- Understand ownership: Know which function returns a new reference and which functions transfer ownership.
- Use
g_object_ref()carefully: Only acquire additional references when you need independent ownership. - Release references: Every owned reference should eventually be released.
- Use
g_clear_object(): Prefer it when releasing object pointers stored in structures. - Use
g_free()for allocated memory: Do not confuse ordinary memory allocations with GObject instances. - Understand floating references: GTK widgets commonly start with floating references, unlike ordinary GObjects.
- Avoid reference cycles: Use weak references when appropriate.
- Respect GTK version differences: GTK3 and GTK4 have different widget removal and destruction APIs.
10. Quick reference: important memory functions
Function Description
———————————– ———————————–
g_object_new() Creates a GObject.
g_object_ref() Acquires another reference.
g_object_unref() Releases a reference.
g_object_ref_sink() Converts a floating reference into
a normal reference, or acquires a
reference if already sunk.
g_clear_object() Releases a reference and sets the
pointer to NULL.
g_object_weak_ref() Registers a callback when an object
is finalized.
g_malloc() Allocates memory.
g_malloc0() Allocates zero-initialized memory.
g_strdup() Duplicates a string.
g_free() Frees allocated memory.
———————————————————————–
11. Official documentation for further learning
These are useful references from the GNOME project:
- GObject Type System Concepts – Object creation, reference counting, disposal and finalization.
- GNOME Memory Management Guidelines – Ownership, allocation and floating references.
The most important principle to remember: GTK+ memory management is based on ownership. Every reference must have a clear owner, and that owner must release it when it is no longer needed.