私は、可変パラメータリストを取る関数のケースのサブセットを扱う関数への関数ポインタを作成したいと思います。ユースケースでは、...
という関数を特定のパラメータリストを持つ関数にキャストしているため、va_list
やお友達を扱わずに可変パラメータを扱うことができます。Cでは、可変長の関数ポインタを有限の引数を持つ関数ポインタにキャストするのは安全ですか?
次のコード例では、変数パラメータを持つ関数を、ハードコードされたパラメータリストを持つ関数にキャストしています(逆もまた同様です)。これはうまくいく(あるいはうまくいく)が、私が使用している呼び出し規約のために偶然であるかどうかはわかりません。 (私は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
は「この未定義の動作ですか?」ではないだろうはい。互換性のない関数ポインタを介して関数を呼び出すと、常に未定義の動作が発生します。 –
バリデーションコールが使用されているかどうかにかかわらず、スタックにパラメータをプッシュすることは、一貫した方法で行われているようです。だから実際にはスペック(または多分言及されていない?)ごとに "未定義"かもしれませんが、その振る舞いは実際には一貫していませんか? – mpontillo
@Mike:非可変パラメータは、可能であれば(少なくともx86では)CPUレジスタに渡されます。彼らはスタックにまったく押し込まれません。これがうまくいかない理由の1つです。 – AnT