【问题标题】:Dialog creation and destruction loop increases memory usage对话框创建和销毁循环会增加内存使用量
【发布时间】:2017-02-08 16:22:24
【问题描述】:

我编写了一个简单的程序模块来询问用户的个人资料名称。为此,我创建了带有条目小部件的 windoww,并在网格中组织了两个按钮(确定和取消)。当用户输入一个已经存在的配置文件名称时,它会通过创建带有“确定”按钮的对话框来通知他这一事实,并且在他按下它之后,它会返回选择配置文件名称(同时窗口没有隐藏也没有破坏) .问题是,当我创建一个配置文件,然后在配置文件名称选择器和对话框(通过创建和销毁对话框创建一个简单的循环) 程序的内存使用量增加。


TL;DR 简单地创建和销毁 gtk 窗口(和对话框)似乎会导致内存泄漏。让应用程序处于循环状态使其内存使用量增加了约 1900%(从 10mb 到 200mb)。

不,我没有使用专为它设计的应用程序测试内存泄漏。 是的,我设置了 G_SLICE=always-malloc。 是的,在程序的后台运行了另一个线程(但我确信它不会导致任何泄漏) 如果您想了解有关内存中发生的情况的更多信息,我可以从 Windows 性能监视器发布屏幕。

问题是 - 是我造成的内存泄漏,还是 GTK 的错(我听说它有一个惰性的内存管理策略,但一段时间后内存使用量从 200mb 下降到 140mb 并保持在那里)?

代码如下:

// This callback racts to the ok and cancel buttons. If input was correcs
// or the user pressed cancel it destroys the window. Else it show error
// prompt. The showing error prompt seems to be the problem here.
void pickNameButtCB(GtkWidget *button, gpointer *call)
{
    GtkWidget *window = gtk_widget_get_toplevel(button);
    if( *((char*)call) == 'k')
    {
        GList *entryBase = gtk_container_get_children(GTK_CONTAINER(gtk_bin_get_child(GTK_BIN(window)))), *entry = entryBase;
        for(size_t i=g_list_length(entry); i>0 && !GTK_IS_ENTRY(entry->data); --i)
            entry = g_list_next(entry);
        if(gtk_entry_get_text_length(GTK_ENTRY(entry->data)) > 0)
        {
            const char *temp = gtk_entry_get_text(GTK_ENTRY(entry->data));
            char path[266];
            strcpy(path, settingsDir);
            strcat(path, temp);
            strcat(path, ".prof");

            if(settProfExists(path))
            {
                g_list_free(entryBase);
                showError(GTK_WINDOW(window), GTK_MESSAGE_ERROR, "Profile with that name already exists!");
                return;
            }
            // nothing here executes as well
        }
        else
        {
            /** doesn't execute when the memory leak happens */
        }
        g_list_free(entryBase);
    }
    gtk_widget_destroy(window);
    gtk_main_quit();
}

void showError(GtkWindow *parent, GtkMessageType type, const char *str)
{
    GtkWidget *window = gtk_message_dialog_new(parent, GTK_DIALOG_DESTROY_WITH_PARENT, type, GTK_BUTTONS_OK, str);
    g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window);
    gtk_dialog_run(GTK_DIALOG(window));
}

bool settProfExists(const char *path)
{
    if(fileExists(path))
        return true;
    return false;
}

bool fileExists(const char *path)
{
    struct stat info;
    errno = 0;
    if((stat(path, &info)) != 0)
        if(errno == ENOENT)
            return false;
    return true;
}

【问题讨论】:

  • “为我调试代码”不是一个好问题。首先从代码中消除多余的细节,直到达到minimal reproducible example。您可能会在此过程中自己发现问题。
  • 我看到了你创建窗口的函数,但我没有看到任何窗口的实际创建或销毁。
  • @JohnBollinger “chooseProfName”在这里只是为了完整性。它确实创建了在循环中不断打开的窗口。循环实际上是在 pickNameButtCB 中,运行 showError,对话框在此创建和销毁(我将“响应”信号连接到小部件销毁函数)。
  • 如果在消除混乱的同时找不到问题,请发布问题。但我怀疑你迄今为止缺乏成功很大程度上受到细节数量的影响。
  • 这不是您上次编辑中的minimal reproducible example。说真的,花时间减少它(即删除代码)而不是不耐烦。

标签: c windows memory-leaks gtk gtk3


【解决方案1】:

您的代码太不完整,无法找到根本原因。

这一行尤其没有意义。我们缺少 entry 的定义,而您正试图在同一行 (*entry = entryBase;) 上执行多个操作。

GList *entryBase = gtk_container_get_children(GTK_CONTAINER(gtk_bin_get_child(GTK_BIN(window)))), *entry = entryBase;

但是,泄漏可能出现在您调用但未提供代码的函数中(settProfExists、newProfile)。所以请提供一个重现问题的最小可编译示例

至于样式,有几个错误:您遍历列表的方式与处理数组的方式相同。看看 GList 是如何实现的,你会发现这很愚蠢(提示:看看 entry->preventry->next):

   for(size_t i=g_list_length(entry); i>0 && !GTK_IS_ENTRY(entry->data); --i)
        entry = g_list_next(entry);

然后是架构问题:不要试图检查你的 UI 来猜测小部件在哪里,只需创建一个结构,然后将 call 参数和你感兴趣的 GtkEntry 传递给带有 gpointer 的结构回调的数据参数。

您也不应该手动创建路径:您使用的固定长度数组可能不够长。使用 GLib 提供的可移植的 g_build_path(并在使用后使用 g_free 释放路径),它将为您处理 Windows/Linux 目录分隔符。

对于您的对话框,当您运行 gtk_run_dialog 时,您可以在之后直接调用 gtk_destroy_widget 而不是连接信号:

GtkWidget *window = gtk_message_dialog_new(parent, GTK_DIALOG_DESTROY_WITH_PARENT, type, GTK_BUTTONS_OK, str);
gtk_dialog_run(GTK_DIALOG(window));
gtk_widget_destroy(window);

【讨论】:

  • 嗯...在 NULL 发生之前我不喜欢搜索,但我认为它会释放一个寄存器,因此 gcc 会有更好的优化可能性。另外,为什么要检查 GUI 是否有我知道有问题的东西?我的意思是,创建一个仅用于显示永远不会在程序中再次使用的条目对话框的结构是否有意义?至于g_build_path……现在有点晚了。我最近意识到 GLib 的强大功能,一旦我开始重构项目就会记住它。是的,我可以直接销毁小部件而不是连接到信号,谢谢。
  • 这不仅仅是一个优化问题。您的代码不应依赖于 UI 的组织方式,因此您可以在不破坏代码的情况下移动小部件并改善用户体验。
  • 至于优化,您的示例无法扩展:在其中包含 100 个元素的 UI 上,遍历它仍然有意义吗?不,你不应该尝试自省 UI,除非你真的有充分的理由这样做(比如编写一个像 gtk-inspector 这样的自省工具)。还有“过早的优化是万恶之源”。只需对 g_list_length 的调用遍历 整个 列表一次,然后在循环中再次遍历它。除非您确定您的算法是最优的,否则不要在寄存器级别思考。
猜你喜欢
  • 1970-01-01
  • 2016-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-12
相关资源
最近更新 更多