10

変数パラメーター リストを受け取る関数のサブセットを処理する関数への関数ポインターを作成したいと考えています。ユースケースは、パラメーターの特定のリストを受け取る関数に取る関数をキャストする...ため、友人を扱わずに変数パラメーターを処理できますva_list

次のコード例では、変数パラメーターを持つ関数を、ハードコードされたパラメーター リストを持つ関数にキャストしています (逆も同様です)。これは機能します (またはたまたま機能します) が、使用中の呼び出し規約による偶然かどうかはわかりません。(2 つの異なる 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

はい、これは未定義の動作です。

現在のコンパイラ、プラットフォーム、およびパラメーターの型に合わせてポインターが並んでいるため、たまたまうまくいきます。double やその他の型でこれを試してみると、奇妙な動作を再現できる可能性があります。

そうしないとしても、これは非常に危険なコードです。

可変引数に悩まされていると思います。共用体または構造体で共通のパラメーター セットを定義し、それを渡すことを検討してください。

于 2012-07-30T23:45:09.143 に答える