Reputation: 661
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.
Upvotes: 2
Views: 265
Reputation: 4299
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
Upvotes: 1
Reputation: 661
Found the solution. It was by no means trivial as it required looking at what Vala was doing and looking at the gtree.c source code to understand what was happening to the allocated memory of the trees.
Because Vala calls g_tree_unref
on the root tree by default at the end of the program, the root tree is freed, but the memory chunks of the nested trees that were part of it are lost and not freed. One has to make Vala call g_tree_unref
on those nested trees. A way to go about it is by owning the references to the nested trees. That can be done in the foreach TraverseFunc
of the root tree in the following manner
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);
// Iterate through the tree and get a strong reference to the values
// to free them
tree.@foreach ((TraverseFunc<int, Tree<int, string>>)valueDestroyThroughTraversing);
c_function (out tree);
tree.@foreach ((TraverseFunc<int, Tree<int, string>>)valueDestroyThroughTraversing);
return 0;
}
public bool valueDestroyThroughTraversing (int treeKey, owned Tree<int, string> treeValue) {
// Do something with the keys and values of the tree if desired
// treeValue will go out of scope at the end of the method
// and Vala will free it
return false;
}
main.c
#include <stdio.h>
#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++) {
GTree * nestedTree = g_tree_new ((GCompareFunc)treeCompareFunction);
g_tree_insert (nestedTree, (gpointer) ((gintptr)i), "value 1");
g_tree_insert (*tree, (gpointer) ((gintptr)i), nestedTree);
}
}
gint treeCompareFunction (gint a, gint b) {
if (a < b) return -1;
if (a == b) return 0;
return 1;
}
Upvotes: -1