调用calloc - 内存泄漏 valgrind

5

以下代码是NCURSES菜单库的一个示例。我不确定代码有什么问题,但valgrind报告了一些问题。有什么想法...

==4803== 1,049 (72 direct, 977 indirect) bytes in 1 blocks are definitely lost in loss record 25 of 36
==4803==    at 0x4C24477: calloc (vg_replace_malloc.c:418)
==4803==    by 0x400E93: main (in /home/gerardoj/a.out)
==4803== 
==4803== LEAK SUMMARY:
==4803==    definitely lost: 72 bytes in 1 blocks
==4803==    indirectly lost: 977 bytes in 10 blocks
==4803==      possibly lost: 0 bytes in 0 blocks
==4803==    still reachable: 64,942 bytes in 262 blocks

源代码:

#include <curses.h>
#include <menu.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD   4

char *choices[] = {
    "Choice 1",
    "Choice 2",
    "Choice 3",
    "Choice 4",
    "Choice 5",
    "Choice 6",
    "Choice 7",
    "Exit",
}
;

int main()
{
    ITEM **my_items;
    int c;
    MENU *my_menu;
    int n_choices, i;
    ITEM *cur_item;

    /* Initialize curses */
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    /* Initialize items */
    n_choices = ARRAY_SIZE(choices);
    my_items = (ITEM **)calloc(n_choices + 1, sizeof(ITEM *));
    for (i = 0; i < n_choices; ++i) {
        my_items[i] = new_item(choices[i], choices[i]);
    }
    my_items[n_choices] = (ITEM *)NULL;

    my_menu = new_menu((ITEM **)my_items);

    /* Make the menu multi valued */
    menu_opts_off(my_menu, O_ONEVALUE);

    mvprintw(LINES - 3, 0, "Use <SPACE> to select or unselect an item.");
    mvprintw(LINES - 2, 0, "<ENTER> to see presently selected items(F1 to Exit)");
    post_menu(my_menu);
    refresh();

    while ((c = getch()) != KEY_F(1)) {
        switch (c) {
        case KEY_DOWN:
            menu_driver(my_menu, REQ_DOWN_ITEM);
            break;
        case KEY_UP:
            menu_driver(my_menu, REQ_UP_ITEM);
            break;
        case ' ':
            menu_driver(my_menu, REQ_TOGGLE_ITEM);
            break;
        case 10:
            {
                char temp[200];
                ITEM **items;

                items = menu_items(my_menu);
                temp[0] = '\0';
                for (i = 0; i < item_count(my_menu); ++i)
                if(item_value(items[i]) == TRUE) {
                    strcat(temp, item_name(items[i]));
                    strcat(temp, " ");
                }
                move(20, 0);
                clrtoeol();
                mvprintw(20, 0, temp);
                refresh();
            }
            break;
        }
    }
    unpost_menu(menu);
    free_item(my_items[0]);
    free_item(my_items[1]);
    free_item(my_items[2]);
    free_item(my_items[3]);
    free_item(my_items[4]);
    free_item(my_items[5]);
    free_item(my_items[6]);
    free_item(my_items[7]);
    free_menu(my_menu);
    endwin();
}

2
你用"-g"编译了吗?你应该这样做。 - user172818
我试过了,但是一直得到相同的错误。 gcc -g -lcurses -lmenu sample.c - Mike
使用-g选项,Valgrind将能够引用符号/行号等信息,从而准确地显示内存泄漏的位置。 - Tim Post
4个回答

4
根据 NCURSES编程指南,使用菜单库需要以下步骤:
  • 初始化curses。
  • 使用 new_item() 创建项目。您可以为项目指定名称和描述。
  • 使用 new_menu() 创建菜单,通过指定要附加的项目。
  • 使用 menu_post() 并刷新屏幕发布菜单。
  • 使用循环处理用户请求,并使用 menu_driver 进行必要的更新菜单。
  • 使用 menu_unpost() 取消发布菜单。
  • 使用 free_menu() 释放分配给菜单的内存。
  • 使用 free_item() 释放分配给项目的内存。
  • 结束 curses。
  • 从您的代码中可以看出:
    • 您没有取消发布菜单(这可能会导致泄漏,或者可能会破坏屏幕)。
    • 在释放项目之前释放了菜单(我想这可能取决于 ncurses 的实现方式,可能是一个问题,也可能不是一个问题)。
    • 只有8个元素数组中的0和1项被释放。这可能是一个泄漏。
    • my_items 指针数组从未被释放。这肯定是泄漏。
    如 @lh3 所说,使用 -g 选项编译将使 Valgrind 给出丢失内存的行号。 编辑(针对您的评论):my_items 是一个动态分配的指向动态创建的菜单项的指针数组。换句话说,您有一个动态内存块,它包含一堆指向一堆动态分配的 ncurses 结构(菜单项)的指针。因此,完成后要清理,需要释放每个动态分配的 ncurses 结构,然后需要释放保存指向这些结构的指针的内存块。
    换句话说,每个 callocmalloc 都需要一个 free,每个 new_item 都需要一个 free_item,以此类推。
    for (i = 0; i < n_choices; ++i) {
        free_item(my_items[i]);
    }
    free(my_items);
    

    请问您能否详细说明一下“my_items指针数组从未被释放”的情况? - Mike
    看起来我们都是同一个恶意投票者的受害者。 - Tim Post
    有趣的是,ncurses文档中没有提到“free(my_items);”。但是当我添加它时,“definitely lost”字节计数从184变为96。谢谢@josh-kelly。 - Andy Alt

    1

    关于Valgrind的一些注意事项(这经常在Valgrind用户邮件列表中提到):

    still reachable: 64,942 bytes in 262 blocks
    

    这只是在 main() 在退出时仍然可达的引用块,任何现代内核下都会被操作系统回收。

    虽然在调用exit之前明确释放每个已分配的块是一个好的做法,但这并不是技术上的泄露内存,因为在退出时仍然可以到达。

    Josh Kelly建议的那样,重点关注直接、间接和可能丢失的块。这只是答案的补充,已经指出了泄漏来源的可能性。


    0
    free_item(my_items[7]);
    free(my_items);
    

    0

    也许尝试使用--leak-check=full选项运行valgrind?


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接