Is it safe to call a C function with more arguments than it expects?
Asked Answered
R

3

10

I'm repeatedly bumping into the problem of setting up signal handlers in GTK+ code, not needing several of the parameters and tempted to use the same function as the handler for several signals, whose handlers have different signatures - but with the first N arguments (the ones I care about) the same.

Is it safe (in the sense that it isn't undefined behaviour, rather than the more pragmatic sense of "does it work on my PC?") to pass pointers to functions to the GObject API when those functions expect fewer arguments than they will actually get from the signal emission process?

Or, to divorce this from GTK+, is this code okay?

/* Note: No void *userdata argument! */
void show(int x) {
  printf("x = %d\n", x);
}

void do_stuff(void (*fn)(int, void *), void *userdata) {
  static int total = 0;
  (*fn)(total, userdata);
  total++;
}

void doitnow(void) {
  do_stuff(&show, NULL);
}

For extra credit, discuss the implications of different return value types between function signature and call site.

Edit: An almost-identical question probes "compatible function type" more closely, and draws an answer directly addressing my concrete problem - that of chaining GObject signal handlers. TL;DR: Yes, it's undefined behaviour, but it's practically idiomatic (though not obligatory) in some toolkits.

Repentance answered 24/4, 2013 at 10:53 Comment(8)
I am almost certain that converting the function pointer in that way is UB.Hesperus
@Flexo Converting the function pointer is okay. Using it for anything other than converting back, on the other hand, ...Dangerous
BTW, no need for (*fn) in do_stuff. Just call fn(total, userdata). Since fn is a function pointer, the function call operator (total, userdata) does exactly what you expect.Tannie
Point to ponder: How hard is it to write this with proper code--instead of relying on undefined behavior doing what you expect? Writing a few extra functions taking as many args as needed and then calling the one arg function strikes me as the proper way to tackle this pseudo problem, no? Guaranteed and defined behavior beats gross hacks any day.Tannie
@Tannie I don't quite agree that "gross hacks" whose intent is clear and which happen to work everywhere a toolkit runs are necessarily worse than a bunch of boilerplate code necessary only to placate language purists. But then again, I like to write (*fn) because it seems more pure. :)Repentance
@BerndJendrissek Well, I accept the language purist badge, but maintain that the happens to work everywhere a toolkit runs is bad software engineering. How do you know? Are you in control of the toolkit? You rely on your limited imagination being correct and derive an assumption; all this when a portable solution is a few lines of code away. There is no excuse. :-) +1 for a good question, though.Tannie
@Tannie Ok, here's a challenge: find me a platform where GTK+ runs but this impure technique causes actual breakage. I'm not in control of the toolkit, but in this case the toolkit even documents the wrong function signature for you to implement! What to do now?Repentance
@BerndJendrissek What to do now? Become a language purist and send a bug report against GTK+ in the name of clean software engineering, ideally with a patch that does the Right Thing. Please forgive me insisting, I am in spaceflight SW engineering and we do care for details.Tannie
D
8

It's explicitly undefined behaviour, per 6.5.2.2, paragraph 9:

If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.

The type that show is defined with,

void show(int x)

is not compatible with the type of the expression pointed to by the pointer through which it is called,

void (*fn)(int, void *)

Also explicitly in 6.3.2.3, paragraph 8:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

For functions, "compatible type" is characterised in 6.7.6.3 (15) [6.7.5.3 (15) in C99]:

For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types. If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions. If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument promotions to the type of the corresponding identifier. (In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.)

The most directly pertinent part here is that the number of arguments must be the same.

Dangerous answered 24/4, 2013 at 11:16 Comment(3)
howd did you search so quickly? do you know the standard at your finger tips?:-)Freeland
Your answer helped me find the other half of the answer - the meaning of "compatible types" in relation to functions. Would you like to add it here, before I accept this? Apparently it's 6.7.5.3 §15.Repentance
@BerndJendrissek Done, you're right, it's not complete without that.Dangerous
E
4

this is undefined behaviour in the C standard, but the C standard also mandates support for functions with variadic arguments, and the only way compilers implement the latter is to allow the former.

this particular idiomatic form is supported by every compiler and platform supported by GLib, and has been for many years — even before GLib was created — so it's highly unlikely it will ever be broken.

Ela answered 24/4, 2013 at 19:48 Comment(2)
Counterpoint: 20 years ago I used MS QuickC, which one could tell to use the "pascal" calling convention (reversing order of arguments on the stack - pushing first argument first rather than last). Getting the number of arguments wrong in such a case would break. Of course a toolkit is free to define away those platforms where this is a problem, and support only the ones where it isn't.Repentance
so you told a very old compiler on a platform that is not supported in any way by GLib, years before GLib was even thought of, to do something that emulates another language and things broke. this would be a problem... how? :-)Ela
E
0

read http://www.unixwiz.net/techtips/win32-callconv-asm.html and http://www.csee.umbc.edu/~chang/cs313.s02/stack.shtml Seems if you are passing more, it should not cause a problem because first the parameters are getting pushed. So whatever is not needed, stays into caller stack. But the opposite way will surely cause problem.

Elaelaborate answered 24/4, 2013 at 11:17 Comment(3)
That may happen on some implementations but it doesn't change that it's undefined behaviour per the standard.Hesperus
It might be OK if the caller cleans up the stack, but what about the callee - will it not retrieve incorrect parameters due to the oversize parameter stack frame?Borreri
If the orders are correct inside the correct parameters, rest of the parameters are actually lying below those parameters in stack frame, below return address, below base pointer. So when the callee is reading the parameters, it reads up to those it is supposed to read, rest are just lying there untouched. But among those which callee is supposed to read, if anything is wrong, then there will be problem.Elaelaborate

© 2022 - 2024 — McMap. All rights reserved.