10

我想创建一个指向函数的函数指针,该函数将处理采用可变参数列表的函数的案例子集。用例是将一个...接受特定参数列表的函数强制转换为接受特定参数列表的函数,因此您可以在不处理va_list和朋友的情况下处理可变参数。

在下面的示例代码中,我将一个带有可变参数的函数转换为一个带有硬编码参数列表的函数(反之亦然)。这有效(或恰好有效),但由于使用的调用约定,我不知道是否是巧合。(我在两个不同的基于 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;
}

这是未定义的行为吗?如果是的话,任何人都可以举一个这样的平台示例,或者在给定更复杂的用例的情况下,如何调整我的示例以使其失败?


编辑:为了解决此代码会因更复杂的参数而中断的含义,我扩展了我的示例:

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

它仍然有效。谁能指出什么时候会失败的具体例子?

$ 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
4

2 回答 2

8

将指针转换为不同的函数指针类型是非常安全的。但是该语言唯一保证的是您可以稍后将其转换回原始类型并获得原始指针值。

通过强制转换为不兼容的函数指针类型的指针调用函数会导致未定义的行为。这适用于所有不兼容的函数指针类型,无论它们是否可变。

您发布的代码会产生未定义的行为:不是在强制转换时,而是在调用时。


试图在“它会失败的地方”追逐示例是一项毫无意义的努力,但无论如何它应该很容易,因为参数传递约定(低级和语言级)有很大不同。例如,下面的代码在实践中通常不会“工作”

void foo(const char *s, float f) { printf(s, f); }

int main() {
  typedef void (*T)(const char *s, ...);
  T p = (T) foo;
  float f = 0.5;
  p("%f\n", f);
}

打印零而不是0.5(GCC)

于 2012-07-30T23:52:48.383 回答
4

是的,这是未定义的行为。

它恰好起作用,因为指针正在为您当前的编译器、平台和参数类型排列。尝试使用双打和其他类型进行此操作,您可能能够重现奇怪的行为。

即使您不这样做,这也是非常危险的代码。

我假设您对可变参数感到恼火。考虑在联合或结构中定义一组通用参数,然后将其传递。

于 2012-07-30T23:45:09.143 回答