将Vala与C结合使用时会导致内存泄漏

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将Vala与C结合使用时会导致内存泄漏相关的知识,希望对你有一定的参考价值。

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

Meyinvala

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函数一次没有发生内存泄漏,但如果我第二次调用它,main.c的第10行在for循环中创建一个Tree会导致内存泄漏。

代码编译用

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代码中的out参数。因此,在第二次调用时,应释放分配给tree的第一个值。我希望Vala生成一个调用来执行此操作,但我没有编写任何示例代码来检查。您可以使用--ccode开关将您的Vala代码编译为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时使用GDestroyNotify作为g_tree_new_full函数。

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中完成

Meyinvala

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

以上是关于将Vala与C结合使用时会导致内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

如何使用模块化代码片段中的LeakCanary检测内存泄漏?

使用导致内存泄漏的音频片段

带有 UI 和内存泄漏的保留片段

性能测试--jvm中内存泄露与溢出

为啥将按钮添加为子视图时会出现内存泄漏?

内存泄漏与溢出