gtk2.0给回调函数传递字符串造成的悬垂指针的问题

⌚Time: 2025-06-17 18:25:00

👨‍💻Author: Jack Ge

一个菜单项的回调函数


static void radio_menu_callback(GtkWidget *widget, gpointer data) {
    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) {
        char *str = (char *)data;
        g_print("%s\n", str);
    }
}

我给回调函数直接传递了一个字符串,导致回调函数里打印出错误内容。

std::string any = "Hello";
g_signal_connect(
    item771, 
    "toggled", 
    G_CALLBACK(radio_menu_callback), 
    any.c_str()
);

原因就是std::string::c_str() 返回的是一个 临时指针,它仅在 any 字符串对象存活时有效。如果 any 被销毁(比如超出作用域或内容被修改),而回调函数仍在尝试访问该指针,会导致 悬垂指针(Dangling Pointer),引发未定义行为(崩溃或乱码)。

很明显函数结束后字符串被释放,指针指向了无效区域。

解决办法就是使用g_strdup拷贝字符串到堆,在回调函数里手动释放堆内存。

// 回调函数中释放内存
static void radio_menu_callback(GtkWidget *widget, gpointer data) {
    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) {
        char *str = (char *)data;
        g_print("%s\n", str);
        g_free(str); // 必须释放!
    }
}

...
// 连接信号时复制字符串
std::string any = "Hello";
g_signal_connect(
    item771, 
    "toggled", 
    G_CALLBACK(radio_menu_callback), 
    g_strdup(any.c_str())  // 深拷贝字符串到堆内存
);

但是这样会不会造成回调函数连接时只拷贝一次,但是执行多次回调函数造成多次释放而崩溃的问题呢?我测试了没有这个问题,但是按理说是有这个问题的。所以最好的办法就是在回调函数里不进行释放。在程序退出时统一释放。

// 回调函数中释放内存
static void radio_menu_callback(GtkWidget *widget, gpointer data) {
    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) {
        char *str = (char *)data;
        g_print("%s\n", str);
        //回调函数不释放
    }
}

...
// 连接信号时复制字符串
std::string any = "Hello";

char *menu_data = g_strdup(any.c_str()); // 深拷贝一次
g_signal_connect(
    item771, 
    "toggled", 
    G_CALLBACK(radio_menu_callback), 
    menu_data
);

// 在程序退出时统一释放(如窗口销毁时)
g_signal_connect(
    window, 
    "destroy", 
    G_CALLBACK(g_free), 
    menu_data  // 确保只释放一次
);
...

如果菜单项比较少,就不用写释放代码了,泄漏的内存可以忽略。