In C, is it ever safe to cast a variadic function pointer to a function pointer with finite arguments?

Viewed 4136

I want to create a function pointer to a function that will handle a subset of cases for a function that takes a variable parameter list. The use case is casting a function that takes ... to a function that takes a specific list of parameters, so you can deal with variable parameters without dealing with va_list and friends.

In the following example code, I'm casting a function with a variable parameters to a function with a hard-coded parameter list (and vice versa). This works (or happens to work), but I don't know if's a coincidence due to the calling convention in use. (I tried it on two different x86_64-based platforms.)

#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;
}

Is this undefined behavior? If yes, can anyone give an example of a platform where this won't work, or a way to tweak my example in such a way that it will fail, given a more complex use case?


Edit: To address the implication that this code would break with more complex arguments, I extended my example:

#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;
}

It still works. Can anyone point out a specific example of when this would fail?

$ 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
2 Answers
Related