Voglio creare un puntatore di funzione a una funzione che gestirà un sottoinsieme di casi per una funzione che accetta un elenco di parametri variabili. Il caso d'uso è il casting di una funzione che prende ...
in una funzione che richiede un elenco specifico di parametri, in modo da poter gestire i parametri variabili senza occuparsi di va_list
e di amici.In C, è mai sicuro lanciare un puntatore a funzione variadica su un puntatore a funzione con argomenti finiti?
Nel seguente codice di esempio, sto lanciando una funzione con parametri variabili a una funzione con un elenco di parametri hardcoded (e viceversa). Funziona (o succede a lavorare), ma non so se è una coincidenza dovuta alla convenzione di chiamata in uso. (L'ho provato su due diverse piattaforme basate su x86_64.)
#include <stdio.h>
#include <stdarg.h>
void function1(char* s, ...)
{
va_list ap;
int tmp;
va_start(ap, s);
tmp = va_arg(ap, int);
printf(s, tmp);
va_end(ap);
}
void function2(char* s, int d)
{
printf(s, d);
}
typedef void (*functionX_t)(char*, int);
typedef void (*functionY_t)(char*, ...);
int main(int argc, char* argv[])
{
int x = 42;
/* swap! */
functionX_t functionX = (functionX_t) function1;
functionY_t functionY = (functionY_t) function2;
function1("%d\n", x);
function2("%d\n", x);
functionX("%d\n", x);
functionY("%d\n", x);
return 0;
}
È un comportamento non definito? Se sì, qualcuno può dare un esempio di una piattaforma in cui questo non funzionerà, o un modo per modificare il mio esempio in modo tale che fallirà, dato un caso d'uso più complesso?
Modifica: Per affrontare le implicazioni che questo codice avrebbe rotto con argomenti più complessi, ho esteso il mio esempio:
#include <stdio.h>
#include <stdarg.h>
struct crazy
{
float f;
double lf;
int d;
unsigned int ua[2];
char* s;
};
void function1(char* s, ...)
{
va_list ap;
struct crazy c;
va_start(ap, s);
c = va_arg(ap, struct crazy);
printf(s, c.s, c.f, c.lf, c.d, c.ua[0], c.ua[1]);
va_end(ap);
}
void function2(char* s, struct crazy c)
{
printf(s, c.s, c.f, c.lf, c.d, c.ua[0], c.ua[1]);
}
typedef void (*functionX_t)(char*, struct crazy);
typedef void (*functionY_t)(char*, ...);
int main(int argc, char* argv[])
{
struct crazy c =
{
.f = 3.14,
.lf = 3.1415,
.d = -42,
.ua = { 0, 42 },
.s = "this is crazy"
};
/* swap! */
functionX_t functionX = (functionX_t) function1;
functionY_t functionY = (functionY_t) function2;
function1("%s %f %lf %d %u %u\n", c);
function2("%s %f %lf %d %u %u\n", c);
functionX("%s %f %lf %d %u %u\n", c);
functionY("%s %f %lf %d %u %u\n", c);
return 0;
}
Funziona ancora. Qualcuno può indicare un esempio specifico di quando questo fallirebbe?
$ gcc -Wall -g -o varargs -O9 varargs.c
$ ./varargs
this is crazy 3.140000 3.141500 -42 0 42
this is crazy 3.140000 3.141500 -42 0 42
this is crazy 3.140000 3.141500 -42 0 42
this is crazy 3.140000 3.141500 -42 0 42
"È questo il comportamento indefinito?" Sì. Chiamare una funzione tramite un puntatore di funzione incompatibile genera sempre un comportamento non definito. –
Mi sembra (almeno con l'esempio) che i parametri push nello stack vengano eseguiti in modo coerente, indipendentemente dal fatto che venga utilizzata o meno una chiamata variadic. Quindi, anche se può essere effettivamente "indefinito" secondo le specifiche (o forse non menzionato?), Il comportamento è in realtà incoerente? – mpontillo
@Mike: i parametri non-variadici vengono passati attraverso i registri della CPU quando possibile (almeno su x86). Non vengono affatto messi in pila. Questa è una delle ragioni per cui non funzionerà. – AnT