is casting a variadic function to a non-variadic one legal?

Discussion in 'C Programming' started by Colin Walters, Feb 13, 2004.

  1. Hi,

    I have a program which had a little function that always returned TRUE,
    it looked like this:

    int
    always_true_function (void *dummy, ...)
    {
    return 1;
    }

    Now, at one point I was calling this via a function pointer that was
    defined without the varargs, like this:

    typedef int (*testfunc) (void *dummy);

    int
    main (int argc, char **argv)
    {
    testfunc f = (testfunc) always_true_function;
    if (f (NULL))
    return 0;
    return 1;
    }

    This all worked fine up until recently, when I heard from one user who was
    trying to run the program on AMD64 was getting very mysterious segfaults.

    On my machine (i386, gcc 3.3.2), the above test program works fine. On
    his machine (AMD64, gcc 3.3.2 + propolice patch), it segfaults.

    It turned out I didn't actually need the varargs, but I'm curious - who is
    at fault here? Is my code wrong, or is his gcc broken?
    Colin Walters, Feb 13, 2004
    #1
    1. Advertising

  2. Colin Walters

    Chris Torek Guest

    In article <news:p-state.edu>
    Colin Walters <-state.edu> writes:
    >int
    >always_true_function (void *dummy, ...)
    >{
    > return 1;
    >}
    >
    >Now, at one point I was calling this via a function pointer that was
    >defined without the varargs, like this:
    >
    >typedef int (*testfunc) (void *dummy);
    >
    >int
    >main (int argc, char **argv)
    >{
    > testfunc f = (testfunc) always_true_function;
    > if (f (NULL))
    > return 0;
    > return 1;
    >}
    >
    >This all worked fine up until recently, when I heard from one user who was
    >trying to run the program on AMD64 was getting very mysterious segfaults.
    >
    >On my machine (i386, gcc 3.3.2), the above test program works fine. On
    >his machine (AMD64, gcc 3.3.2 + propolice patch), it segfaults.
    >
    >It turned out I didn't actually need the varargs, but I'm curious - who is
    >at fault here? Is my code wrong, or is his gcc broken?


    Your code is technically wrong (or "illegal" or any other number of
    words that do not really carry the right meaning). Specifically,
    the call:

    f(NULL)

    invokes undefined behavior by calling a function through a pointer
    that has the wrong type. In this case, the AMD64 system probably
    uses different call sequences for "varying arguments" vs "fixed
    arguments", but in principle this can even happen if you call a
    function whose actual type is (e.g.) void(void *) instead of
    int(void *).

    You can correct the call (without any structural changes to the
    code itself, such as changing the name and/or type of
    always_true_function()) in at least two ways:

    if (((int (*)(void *, ...))f)(NULL)) ...

    will convert the function pointer stored in "f" back to the correct
    type, then (correctly) call the actual function always_true_function();
    or you can insert an intermediate function:

    int always_true_function(void *dummy, ...) { return 1; }

    int adapter_for_always_true_function(void *passthrough) {
    return always_true_function(passthrough);
    }
    ...
    testfunc f = adapter_for_always_true_function;

    The adapter serves as a sort of "pipe fitting" to connect the
    "two-inch pipeline" (varying argument function, in this case)
    to the "3/4-inch hose" (non-varying call).

    The cast method works, but requires great care -- you must always
    match the converted-to type just before the call to the actual
    type of the target function. This may well defeat the entire
    purpose of using function pointers in the first place, so the
    adapter method is more general.

    It also acts as a kind of lesson: any time you have a pointer cast
    in C code, be suspicious. If there is a pointer-cast-free way to
    rewrite the code, that second version probably has fewer bugs. :)
    --
    In-Real-Life: Chris Torek, Wind River Systems
    Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
    email: forget about it http://web.torek.net/torek/index.html
    Reading email is like searching for food in the garbage, thanks to spammers.
    Chris Torek, Feb 13, 2004
    #2
    1. Advertising

  3. Colin Walters

    Ben Pfaff Guest

    Colin Walters <-state.edu> writes:

    > I have a program which had a little function that always returned TRUE,
    > it looked like this:
    >
    > int
    > always_true_function (void *dummy, ...)
    > {
    > return 1;
    > }
    >
    > Now, at one point I was calling this via a function pointer that was
    > defined without the varargs, like this:
    >
    > typedef int (*testfunc) (void *dummy);


    That's undefined behavior. You're not supposed to call a varargs
    function through a non-varargs prototype.
    --
    int main(void){char p[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.\
    \n",*q="kl BIcNBFr.NKEzjwCIxNJC";int i=sizeof p/2;char *strchr();int putchar(\
    );while(*q){i+=strchr(p,*q++)-p;if(i>=(int)sizeof p)i-=sizeof p-1;putchar(p\
    );}return 0;}
    Ben Pfaff, Feb 13, 2004
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Ajax Chelsea
    Replies:
    1
    Views:
    430
    Ron Natalie
    Dec 2, 2003
  2. Ross A. Finlayson
    Replies:
    19
    Views:
    590
    Keith Thompson
    Mar 10, 2005
  3. Replies:
    2
    Views:
    343
    Dave Thompson
    Feb 27, 2006
  4. Replies:
    5
    Views:
    363
  5. Qi
    Replies:
    1
    Views:
    391
Loading...

Share This Page