你可以将指向一种类型的函数的指针转换为另一种类型的函数来获取额外的参数吗?

时间:2010-01-07 18:39:39

标签: c gtk

你可以转换这种类型的函数指针:

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 +开发人员找到了一些合法的魔力来使这一切都有效吗?

3 个答案:

答案 0 :(得分:2)

C调用约定使调用者负责清理堆栈上的参数。因此,如果调用者提供了太多参数,那么这不是问题。其他参数只是被忽略了。

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

答案 1 :(得分:1)

在我看来,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来管理具有不同参数模式的回调,但这是一个不同的问题。

答案 2 :(得分:1)

现在,这有多酷,我目前正在通过GTK教程,并且发现了完全相同的问题。

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

您的问题的答案(改编自上述问题的优秀答案以及问题Function pointer cast to different signature的答案):

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

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