tags:

views:

196

answers:

3

Can you cast a function pointer of this type:

void (*one)(int a)

to one of this type:

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

and then safely invoke the pointed-to function with the additional arguments(s) it has been cast to take? I had thought such a thing was illegal, that both function types had to be compatible. (Meaning the same prototype--same return value, same parameter list.) But that is exactly what this bit of GTK+ code appears to be doing (taken from here):

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

If you look up the "clicked" signal (or just look at other examples of its use from the first link), you will see that its handlers are expected to be declared like this:

void user_function(GtkButton *button, gpointer user_data);

When you register a handler via g_signal_connect_swapped(), the widget pointer and data pointer arguments are swapped in order, thus, the declaration should look like this instead:

void user_function(gpointer user_data, GtkButton *button);

Here is the problem. The gtk_widget_destroy() function registered as a callback is prototyped like this:

void gtk_widget_destroy(GtkWidget *widget);

to take only a single argument. Presumably, because the data pointer (a GtkWindow) and the pointer to the signaling widget (a GtkButton) are swapped, the sole argument it receives will be the window pointer, and the button pointer, which will be passed after, will silently be ignored. Some Googling has turned up similar examples, even the registering of functions like gtk_main_quit() that take no arguments at all.

Am I correct in believing this to be a standards violation? Have the GTK+ developers found some legal magic to make this all work?

+1  A: 

The C calling convention makes it the responsibility of the caller to clean up the arguments on the stack. So if the caller supplies too many arguments, it is not a problem. The additional arguments are just ignored.

So yes, you can cast a function pointer to another function pointer type with the same arguments and then some, and call the original function with too many arguments and it will work.

Arve
+1  A: 

In my opinion, C89 standards in this regard are quite confusing. As far as I know, they don't disallow casting from/to function with no param specification, so:

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);

At the end, the compilers must be able to cast from one to two, so I don't see the reason to forbid the direct cast.

Anyway, signal handling in GTK+ uses some dark magic behind the scenes to manage callbacks with different argument patterns, but this is a different question.

ntd
A: 

Now, how cool is that, I'm also currently working my way through the GTK tutorial, and stumbled over exactly the same problem.

I tried a few examples, and then asked question http://stackoverflow.com/questions/2118889/what-happens-if-i-cast-a-function-pointer-changing-the-number-of-parameters/ , with a simplified example.

The answer to your question (adapted from the excellent answers to my question above, and the answers from question http://stackoverflow.com/questions/188839/function-pointer-cast-to-different-signature):

  • Yes, this is a violation of the C standard. If you cast a function pointer to a function pointer of a different type, you must cast it back to its original type before calling it. Anything else is undefined behaviour.
  • However, what it does in practice depends on the C compiler, especially the calling convention it uses. The most common calling convention (at least on i386) happens to just put the parameters on the stack in reverse order, so if a function needs less parameters than it is supplied, it will just use the first parameters and ignore the rest -- which is just what you want. This will break however on platforms with different calling conventions.

So the real question is why the GLib developers did it like this. But that's a different question I guess...

sleske