The following Vala code combined with C is causing memory leaks and I can't wrap my head around it.
Main.vala
using GLib;
[CCode (cname = "c_function")]
public static extern void c_function (out Tree<int, Tree<int, string>> tree);
public static int main () {
Tree<int, Tree<int, string>> tree;
c_function (out tree);
// If code were to end here and return 0, no memory leak happens, but
// if we call c_function again, memory leak happens according to valgrind
c_function (out tree); // Leak happens on this second call
return 0;
}
main.c
#include <glib.h>
gint treeCompareFunction (gint a, gint b);
void extern c_function (GTree **tree) {
*tree = g_tree_new ((GCompareFunc)treeCompareFunction);
for (int i = 0; i < 3; i++) {
// Memory leak in the next line when function is called a second time
GTree * nestedTree = g_tree_new ((GCompareFunc)treeCompareFunction);
g_tree_insert (nestedTree, i, "value 1");
g_tree_insert (*tree, i, (gpointer) nestedTree);
}
}
gint treeCompareFunction (gint a, gint b) {
if (a < b) return -1;
if (a == b) return 0;
return 1;
}
I don't understand why if I call the C function only once no memory leak happens, but if I call it a second time, line 10 of main.c which creates a Tree in a for loop causes a memory leak.
The code is compiled with
valac Main.vala main.c -g
And then run with
valgrind --leak-check=yes ./Main
I would like to know if it is possible to work around it. I tried emptying the tree in the Vala code before calling the C function for the second time. No success. Also tried destroying the tree passed as argument if it wasn't NULL on the second call of the C function. No success either. Still getting memory leaks.
Looking at the code you've supplied I would look into using g_tree_new_full ()
instead of g_tree_new ()
in your C code.
You are re-using tree
as an out argument in the Vala code. So on the second call the first value assigned to tree
should be freed. I'm hoping Vala generates a call to do that, but I've not written any sample code to check. You can compile your Vala code with the --ccode
switch to valac
to check the generated C.
So long as Vala is calling g_tree_unref ()
then it is the set up of your C code that is not freeing the nested tree. You need a GDestroyNotify
function for the nested tree to be passed to g_tree_new_full ()
.
The error is in your C code. Your C code should be:
#include <glib.h>
gint treeCompareFunction (gint a, gint b);
void extern c_function (GTree **tree) {
*tree = g_tree_new_full ((GCompareFunc)treeCompareFunction,
NULL,
NULL,
g_tree_unref
);
for (int i = 0; i < 3; i++) {
GTree * nestedTree = g_tree_new ((GCompareFunc)treeCompareFunction);
g_tree_insert (nestedTree, i, "value 1");
g_tree_insert (*tree, i, (gpointer) nestedTree);
}
}
gint treeCompareFunction (gint a, gint b) {
if (a < b) return -1;
if (a == b) return 0;
return 1;
}
Note the use of g_tree_unref
as the GDestroyNotify
function when using g_tree_new_full
.
The Valgrind leak summary now reports:
==22035== LEAK SUMMARY:
==22035== definitely lost: 0 bytes in 0 blocks
==22035== indirectly lost: 0 bytes in 0 blocks
==22035== possibly lost: 1,352 bytes in 18 blocks
Before, with the code in your question, the leak summary was:
==21436== LEAK SUMMARY:
==21436== definitely lost: 288 bytes in 6 blocks
==21436== indirectly lost: 240 bytes in 6 blocks
==21436== possibly lost: 1,352 bytes in 18 blocks