将 Vala 与 C 结合使用时出现内存泄漏

Getting memory leak when combining Vala with C

以下 Vala 代码与 C 结合导致内存泄漏,我无法理解它。

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;
}

我不明白为什么如果我只调用一次 C 函数就不会发生内存泄漏,但是如果我第二次调用它,line 10 of main.c 在 for 循环中创建树会导致内存泄漏。

代码用

编译
valac Main.vala main.c -g

然后 运行 和

valgrind --leak-check=yes ./Main

我想知道是否可以解决这个问题。在第二次调用 C 函数之前,我尝试在 Vala 代码中清空树。没有成功。如果在第二次调用 C 函数时它不是 NULL,还尝试销毁作为参数传递的树。也没有成功。仍有内存泄漏。

查看您提供的代码,我会考虑在您的 C 代码中使用 g_tree_new_full () 而不是 g_tree_new ()

您正在重新使用 tree 作为 Vala 代码中的输出参数。所以在第二次调用时,分配给 tree 的第一个值应该被释放。我希望 Vala 生成一个调用来执行此操作,但我没有编写任何示例代码来检查。您可以使用 --ccode 切换到 valac 来编译 Vala 代码以检查生成的 C.

只要 Vala 正在调用 g_tree_unref (),那么就是 C 代码的设置没有释放嵌套树。您需要 GDestroyNotify 函数才能将嵌套树传递给 g_tree_new_full ().

更新

错误在您的 C 代码中。你的 C 代码应该是:

#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;
}

注意在使用g_tree_new_full时将g_tree_unref作为GDestroyNotify函数使用。

Valgrind 泄漏摘要现在报告:

==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

之前,使用您问题中的代码,泄漏摘要是:

==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

找到解决方案。这绝不是微不足道的,因为它需要查看 Vala 在做什么并查看 gtree.c 源代码以了解树的分配内存发生了什么。

因为 Vala 默认在程序结束时调用根树 g_tree_unref ,根树被释放,但是嵌套树的内存块丢失并且没有被释放.必须在那些嵌套树上调用 Vala g_tree_unref。一种解决方法是拥有对嵌套树的引用。这可以通过以下方式在根树的 foreach TraverseFunc 中完成

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;
}