You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GTK中g_signal_connect传递回调参数出现意外值问题求助

问题根源:局部变量生命周期失效

你遇到的问题核心是栈上局部变量的生命周期问题:activate()函数中的abc是局部变量,存储在栈内存中。当activate()执行完毕返回后,栈帧被销毁,这块内存会被后续的函数调用覆盖。虽然回调中拿到的指针地址和之前一致,但内存里的原始值已经不存在了,所以读取到的是随机垃圾值(此处显示为0)。

而你写的普通回调Demo能正常工作,是因为my_val位于main()函数的栈帧中,doing_things()调用时main()还未执行完毕,变量内存依然有效。

正确解决方案

以下是几种符合GTK编程规范的解决方法,避免使用全局变量:

方法1:动态分配内存(推荐)

使用GLib的内存分配函数(或标准C的malloc)在堆上分配数据,这样内存不会随函数返回被释放。记得在不需要时手动释放,避免内存泄漏。

修改后的完整代码:

#include <stdio.h>
#include <gtk/gtk.h>

void hello(GtkWidget *widget, void *data) {
  printf("pointer in hello(): %p\n", data);
  printf("data value: %d\n", *(int*)data);
}

// 窗口销毁时释放动态分配的内存
void on_window_destroy(GtkWidget *widget, gpointer data) {
  g_free(data);
}

void activate(GtkApplication *app, gpointer user_data) {
  GtkWidget *window;
  GtkWidget *button;

  // 在堆上分配内存存储数据
  int *abc = g_malloc(sizeof(int));
  *abc = 5;
  void *ptr = abc;

  window = gtk_application_window_new(app);
  gtk_window_set_title(GTK_WINDOW(window), "Window");
  gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);

  button = gtk_button_new_with_label("Hello World");

  printf("pointer in activate(): %p\n", ptr);
  printf("abc value: %d\n", *(int*)ptr);
  g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(hello), ptr);
  gtk_window_set_child(GTK_WINDOW(window), button);

  // 绑定窗口销毁信号,确保内存被释放
  g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_window_destroy), ptr);

  gtk_window_present(GTK_WINDOW(window));
}

int main(int argc, char **argv) {
  GtkApplication *app;
  int status;

  app = gtk_application_new("org.gtk.example", G_APPLICATION_REPLACE);
  g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
  status = g_application_run(G_APPLICATION(app), argc, argv);
  g_object_unref(app);

  return status;
}

方法2:使用静态变量

将abc声明为静态变量,这样它的生命周期会持续到程序结束。但注意:如果多次调用activate()(比如打开多个窗口),所有实例会共享同一个静态变量,可能导致意外行为,仅适合单窗口场景。

修改activate()中的变量声明:

static int abc = 5;
void *ptr = &abc;

方法3:将数据与GTK对象绑定(符合GTK范式)

使用g_object_set_data()将数据关联到窗口对象上,这样可以通过对象直接获取数据,也能方便地在对象销毁时清理数据。

关键代码片段:

void hello(GtkWidget *widget, void *data) {
  // 从按钮的根窗口对象中获取绑定的数据
  GtkWidget *window = gtk_widget_get_root(widget);
  int *abc = g_object_get_data(G_OBJECT(window), "my-custom-data");
  printf("data value: %d\n", *abc);
}

void activate(GtkApplication *app, gpointer user_data) {
  // ... 其他窗口、按钮创建代码 ...
  int *abc = g_malloc(sizeof(int));
  *abc = 5;
  // 将数据绑定到窗口对象
  g_object_set_data(G_OBJECT(window), "my-custom-data", abc);
  // 点击信号回调无需传参,直接从窗口获取数据
  g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(hello), NULL);
  // 绑定销毁信号释放内存
  g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_window_destroy), abc);
  // ... 其他代码 ...
}
编译与验证

编译命令保持不变:

gcc $(pkg-config --cflags gtk4) -o hello-world-gtk hello-world-gtk.c $(pkg-config --libs gtk4)

运行后点击按钮,会正确输出data value:5。

内容的提问来源于stack exchange,提问作者heroes-of-balkan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 06:41:32