Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c

gtk

gobject

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.

like image 533
Bernd Jendrissek Avatar asked Apr 24 '13 10:04

Bernd Jendrissek


1 Answers

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.

like image 175
Daniel Fischer Avatar answered Nov 15 '22 11:11

Daniel Fischer