Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C99: cast callbacks with different number of arguments

in the following example I make a CAST of a function without arguments in a pointer to a function that should receive an argument. Assuming that it gives the desired result, is it possible that this procedure causes some malfunction? online test: https://onlinegdb.com/SJ6QzzOKI

typedef void (*Callback)(const char*);
Callback cb;

void inserisce_cb(void* c) {
    cb=c;
}

void esegue_cb(){
    cb("pippo");
}

void scriveTitolo(const char* titolo) {
    Uart_Println(titolo);
}

void scriveTitolo2() {
    Uart_Println("pluto");
}

void main(){
    inserisce_cb(scriveTitolo);
    esegue_cb();
    inserisce_cb(scriveTitolo2);
    esegue_cb();
}
like image 920
Ivano Bono Avatar asked Apr 30 '20 08:04

Ivano Bono


2 Answers

Converting a pointer to a function to another pointer to a function is defined by the c standard, but using the resulting pointer to call a function with an incompatible type is not, per C 6.3.2.3 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.

The declaration void scriveTitolo2() { … } defines a function that does not have a parameter type list (it uses the old C style of an identifier list, with that list being empty) and that takes no arguments. A Callback pointer points to a function that has a parameter type list and takes a const char * argument. These are incompatible per C 2018 6.7.6.3 15:

For two function types to be compatible,… 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,…

Since they do not agree in the number of parameters, they are incompatible.

The above speaks only to the issue of converting from void (*)() to void (*){const char *) and using the result to call the function. There is a separate issue in that the function pointer is passed to inserisce_cb, which takes an argument of type void *, which is a pointer to an object type. The C standard does not define the behavior of converting a pointer to a function type to a pointer to an object type. To remedy this, inserisce_cb should be declared to take a pointer to a function type, such as void inserisce_cb(Callback c).

If scriveTitolo2 can be changed, then the compatibility issue can be resolved by changing it to take a const char * parameter that is unused, changing its definition to void scriveTitolo2(const char *).

(Note that it is preferable to declare scriveTitolo2 with the modern C style, as void scriveTitolo2(void) { … }, rather than without the void. This is unrelated to the question, as it would not make the function types compatible, but this format of declaration provides more information to the compiler in many circumstances.)

like image 97
Eric Postpischil Avatar answered Nov 19 '22 01:11

Eric Postpischil


Additional thoughts to Eric's answer, which holds true for C99 as well:

If you call a function with an argument list not compatible to the function's parameter list, this is according to C99 §6.5.2.2 (6) undefined behavior.

It may work, depending on your compiler's ABI. There are compilers that let the called function clean up the stack, other compilers let the caller clean up. The former case will most likely crash, the latter ... who knows.

You can declare your scriveTitolo2 with an ignored parameter:

void scriveTitolo2(const char*) {
    /* ... */
}

And everyone is happy: you and the compiler.

like image 28
the busybee Avatar answered Nov 19 '22 03:11

the busybee