【问题标题】:Getting memory leak when combining Vala with C将 Vala 与 C 结合使用时出现内存泄漏
【发布时间】:2018-05-18 03:38:08
【问题描述】:

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

ma​​in.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 函数,但如果我第二次调用它,ma​​in.cline 10 > 在 for 循环中创建树会导致内存泄漏。

代码用

编译
valac Main.vala main.c -g

然后运行

valgrind --leak-check=yes ./Main

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

【问题讨论】:

    标签: c memory-management memory-leaks valgrind vala


    【解决方案1】:

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

    您在 Vala 代码中重复使用 tree 作为输出参数。所以在第二次调用时,分配给tree 的第一个值应该被释放。我希望 Vala 会生成一个调用来执行此操作,但我还没有编写任何示例代码来检查。您可以编译您的 Vala 代码,将--ccode 切换到valac 以检查生成的 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_unref在使用g_tree_new_full时作为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
    

    【讨论】:

    • 生成的 C 代码看起来不错(准确地说,Vala 调用了g_tree_unref ())。
    • 嗨。感谢您的回答。在嵌套树上设置 GDestroyNotify 函数仍然会报告 valgrind 中的内存泄漏。我还注意到 GLib 文档中可能存在的错误。文本措辞给人的印象是必须在 GDestroyNotify 函数中显式释放内存,但如果尝试这样做,则会引发无效指针错误。当调用函数 g_tree_destroy 或 g_tree_unref 时,键和值会在函数调用结束时自动销毁。
    • 我检查了 gtree.c 源代码,无论是否提供了 DestroyNotify 函数,g_tree_destroy 都会在树节点结构上调用 g_slice_free。另一方面,g_tree_unref 对整个树变量调用 g_slice_free。调用 g_tree_unref 的 Vala 代码不会破坏嵌套树,因为它不会在嵌套树上调用 g_tree_unref,因此不会为这些树调用 g_slice_free。因此,当根树被销毁时,对嵌套树的内存指针引用将丢失。
    • 我认为,如果可以从 Vala 代码中获得对根树的键和值的强引用,就可以设法释放这些引用。问题是,我不知道如何获得对 Vala 代码中的强引用并手动销毁它们。遍历根树给了我对键和值的弱引用。对于我成为这些内存块的所有者来说,这并不是强有力的参考。
    • 因为您使用的是嵌套树,所以您的 GDestroyNotify 函数在对 g_tree_new_full 的 C 代码调用中应该是 g_tree_unref。因此,当 Vala 为父树调用 g_tree_unref 时,GLib 将为节点调用 g_tree_unref
    【解决方案2】:

    找到了解决办法。这绝非易事,因为它需要查看 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;
    }
    

    ma​​in.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;
    }
    

    【讨论】:

    • 我已经更新了我的答案,以展示如何编写 C 代码来释放 Tree 节点。如果您曾经尝试在 Vala 中像这样释放内存,那么很可能还有其他问题。这通常表明应该解决根本问题,而不是尝试使用 Vala 来解决它。
    • 我已将您更新的答案标记为解决方案,因为我认为它更合适。但我的解决方案仍然适用于它可能效率低下。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-09
    • 2013-11-02
    • 1970-01-01
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 2015-06-28
    相关资源
    最近更新 更多