【问题标题】:Can you cast a pointer to a function of one type to a function of another type that takes additional arguments?您能否将指向一种类型的函数的指针转换为另一种类型的带有附加参数的函数?
【发布时间】:2026-01-03 22:25:01
【问题描述】:

你能转换这种类型的函数指针吗:

void (*one)(int a)

到这种类型之一:

void (*two)(int a, int b)

然后使用已强制转换的附加参数安全地调用指向函数?我曾认为这样的事情是非法的,两种函数类型都必须兼容。 (意思是相同的原型——相同的返回值,相同的参数列表。)但这正是这段 GTK+ 代码正在做的事情(取自here):

g_signal_connect_swapped(G_OBJECT(button), "clicked",
                         G_CALLBACK(gtk_widget_destroy), G_OBJECT(window));

如果您查看“clicked”信号(或仅查看第一个链接中的其他使用示例),您会看到它的处理程序应该像这样声明:

void user_function(GtkButton *button, gpointer user_data);

当您通过 g_signal_connect_swapped() 注册处理程序时,小部件指针和数据指针参数按顺序交换,因此,声明应如下所示:

void user_function(gpointer user_data, GtkButton *button);

这就是问题所在。注册为回调的 gtk_widget_destroy() 函数原型如下:

void gtk_widget_destroy(GtkWidget *widget);

只接受一个参数。据推测,因为数据指针(一个 GtkWindow)和指向信号小部件的指针(一个 GtkButton)交换了,它接收的唯一参数将是窗口指针,然后传递的按钮指针将被忽略.一些谷歌搜索已经找到了类似的例子,甚至注册了像 gtk_main_quit() 这样根本不带参数的函数。

我认为这违反标准是否正确? GTK+ 开发人员是否找到了一些合法的魔法来让这一切顺利进行?

【问题讨论】:

标签: c gtk


【解决方案1】:

C 调用约定使调用者有责任清理堆栈上的参数。所以如果调用者提供了太多的参数,这不是问题。其他参数将被忽略。

所以是的,您可以将函数指针转换为具有相同参数的另一个函数指针类型,然后再使用一些参数,并使用太多参数调用原始函数,它会起作用。

【讨论】:

  • 实际上,我相信没有“C 调用约定”之类的东西。 C 标准没有定义它,因此取决于编译器和平台使用什么约定。但是,大多数编译器/平台使用您描述的调用约定。
【解决方案2】:

在我看来,这方面的 C89 标准相当混乱。据我所知,他们不允许在没有参数规范的情况下从/到函数进行转换,所以:

typedef void (*one)(int first);
typedef void (*two)(int first, int second);
typedef void (*empty)();

one src = something;
two dst;

/* Disallowed by C89 standards */
dst = (two) src;

/* Not disallowed by C89 standards */
dst = (two) ((empty) src);

最后,编译器必须能够从one 转换为two,所以我看不出禁止直接转换的原因。

无论如何,GTK+ 中的信号处理在幕后使用了一些 dark magic 来管理具有不同参数模式的回调,但这是一个不同的问题。

【讨论】:

    【解决方案3】:

    现在,这太酷了,我目前也在学习 GTK 教程,并且偶然发现了完全相同的问题。

    我试了几个例子,然后问了What happens if I cast a function pointer, changing the number of parameters的问题,还有一个简化的例子。

    你的问题的答案(改编自我上面的问题的优秀答案,以及问题Function pointer cast to different signature的答案):

    • 是的,这违反了 C 标准。如果将函数指针转换为不兼容类型的函数指针,则必须在调用它之前将其转换回其原始(或兼容)类型。其他任何事情都是未定义的行为。
    • 但是,它在实践中的作用取决于 C 编译器,尤其是它使用的调用约定。最常见的调用约定(至少在 i386 上)恰好只是将参数以相反的顺序放入堆栈,因此如果一个函数需要的参数少于提供的参数,它将只使用第一个参数并忽略其余参数——这正是你想要的。然而,这在具有不同调用约定的平台上中断。

    所以真正的问题是为什么 GLib 开发人员会这样做。但我猜这是一个不同的问题......

    【讨论】: