あなたが説明した方法でそれを行うことはできません。
C 呼び出し規則は、呼び出し元がスタックに引数を配置するためのものですが、型に関する情報は何も配置しないため、呼び出し先はそれを見つける方法 (少なくとも変数のサイズ) を持っている必要があります。
すべてのタイプが既知のプロトタイプを持つ関数については問題ありません。
変数番号またはパラメーター (変数) を持つ関数では、よりトリッキーです。各変数を読み取るには、引数ごとに va_arg を呼び出す必要があり、va_arg の型を指定する必要があります。実際の型ではない型を指定した場合、コンパイラは文句を言いませんが (ランタイム情報ではありません)、何かが起こる可能性があります (通常は何か悪いことです)。
したがって、型を渡す必要があります。
型を予測できる場合もありますが (例: カウンタの後にいくつかの int が続くなど)、通常はパラメータでエンコードして渡します。printf のようにフォーマット文字列でエンコードして渡すことができます。jldupont で説明されているユニオン トリックも十分に一般的です。
しかし、とにかく合格しなければなりません。
データサイズでさえ、基礎となるデータのバイナリ表現に頼ることはできません。プログラムを書いたときに動作しているように見えても、互換性がなく、システムやコンパイラを変更したり、コンパイル オプションを変更したりすると壊れる可能性があります。
型の後に引数を渡す例を見てみましょう (したがって、ユニオン トリックも printf のような書式文字列もありません)。それが行うことは、渡されたすべての値を double に変換して追加することですが、実際には役に立ちません。
#include <stdio.h>
#include <stdarg.h>
enum mytypes {LONG, INT, FLOAT, DOUBLE };
double myfunc(int count, ...){
long tmp_l;
int tmp_i;
double tmp_d;
double res = 0;
int i;
va_list ap;
va_start(ap, count);
for(i=0 ; i < count; i++){
int type = va_arg(ap, enum mytypes);
switch (type){
case LONG:
tmp_l = va_arg(ap, long);
res += tmp_l;
break;
case INT:
tmp_i = va_arg(ap, int);
res += tmp_i;
break;
case FLOAT:
/* float is automatically promoted to double when passed to va_arg */
case DOUBLE:
tmp_d = va_arg(ap, double);
res += tmp_d;
break;
default: /* unknown type */
break;
}
}
va_end(ap);
return res;
}
int main(){
double res;
res = myfunc(5,
LONG, (long)1,
INT, (int)10,
DOUBLE, (double)2.5,
DOUBLE, (double)0.1,
FLOAT, (float)0.3);
printf("res = %f\n", res);
}
この例では、C99 で定義された新しいstdarg 可変長ヘッダーを使用しています。これを使用するには、関数に少なくとも 1 つの固定パラメーターが必要です (この例ではcount
)。したがって、関数にいくつかの可変長リストを含めることができれば良いことです(つまり、 のようなものmyfunc(int count1, ..., int count2, ...)
)。悪い点は、古い形式のように、純粋に可変長関数 (つまり、myfunc(...) のようなもの) を持つことができないことです。varargs互換ヘッダーを使用して、古い形式を引き続き使用できます。しかし、それはより複雑であり、ほとんど必要ありません。 、タイプが必要なだけでなく、リストが終了したことを知る方法やカウントのようなものが便利だからです(ただし、それを行う唯一の方法ではありません。たとえば、「ターミネーター」を使用できます)。