Search code examples
cmemory-managementmemory-leaksvalgrindvala

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.


Solution

  • 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