g_l
g_l

Reputation: 661

Getting memory leak when combining Vala with C

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

Answers (2)

AlThomas
AlThomas

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 ().

Update

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

g_l
g_l

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

Related Questions