質問
可変数の引数を取るC関数があるとします:可変数の引数をその中から期待し、最初の関数に渡されたすべての引数を渡す別の関数を呼び出すにはどうすればよいですか?
例:
void format_string(char *fmt, ...);
void debug_print(int dbg_lvl, char *fmt, ...) {
format_string(fmt, /* how do I pass all the arguments from '...'? */);
fprintf(stdout, fmt);
}
解決
楕円を渡すには、それらをva_listに変換し、2番目の関数でそのva_listを使用する必要があります。具体的には、
void format_string(char *fmt,va_list argptr, char *formatted_string);
void debug_print(int dbg_lvl, char *fmt, ...)
{
char formatted_string[MAX_FMT_SIZE];
va_list argptr;
va_start(argptr,fmt);
format_string(fmt, argptr, formatted_string);
va_end(argptr);
fprintf(stdout, "%s",formatted_string);
}
他のヒント
いたずらで移植性のないトリックにしたくない限り、渡す引数の数を知らずに(たとえば)printfを呼び出す方法はありません。
一般的に使用されるソリューションは、常に別の形式の可変引数関数を提供することです。そのため、 printf
には vprintf
があり、 va_list
...
。 ...
バージョンは、 va_list
バージョンの単なるラッパーです。
壮大なC ++ 0xでは、可変長テンプレートを使用できます:
template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}
template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
format_string(fmt, ts...);
}
関数呼び出しにインラインアセンブリを使用できます。 (このコードでは、引数は文字であると想定しています)。
void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
{
va_list argumentsToPass;
va_start(argumentsToPass, fmt);
char *list = new char[numOfArgs];
for(int n = 0; n < numOfArgs; n++)
list[n] = va_arg(argumentsToPass, char);
va_end(argumentsToPass);
for(int n = numOfArgs - 1; n >= 0; n--)
{
char next;
next = list[n];
__asm push next;
}
__asm push fmt;
__asm call format_string;
fprintf(stdout, fmt);
}
マクロも試すことができます。
#define NONE 0x00
#define DBG 0x1F
#define INFO 0x0F
#define ERR 0x07
#define EMR 0x03
#define CRIT 0x01
#define DEBUG_LEVEL ERR
#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...) if((DEBUG_LEVEL & X) == X) \
DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)
int main()
{
int x=10;
DEBUG_PRINT(DBG, "i am x %d\n", x);
return 0;
}
最初にローカルバッファにフォーマッタを保存することで、フォーマッタを渡すことを解決できますが、これにはスタックが必要であり、対処する必要がある場合があります。フォローしようとしましたが、うまくいくようです。
#include <stdarg.h>
#include <stdio.h>
void print(char const* fmt, ...)
{
va_list arg;
va_start(arg, fmt);
vprintf(fmt, arg);
va_end(arg);
}
void printFormatted(char const* fmt, va_list arg)
{
vprintf(fmt, arg);
}
void showLog(int mdl, char const* type, ...)
{
print("\nMDL: %d, TYPE: %s", mdl, type);
va_list arg;
va_start(arg, type);
char const* fmt = va_arg(arg, char const*);
printFormatted(fmt, arg);
va_end(arg);
}
int main()
{
int x = 3, y = 6;
showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
showLog(1, "ERR");
}
これがお役に立てば幸いです。
Rossのソリューションは少しクリーンアップされました。すべての引数がポインターである場合にのみ機能します。また、言語実装は、 __ VA_ARGS __
が空の場合、前のコンマの省略をサポートする必要があります(Visual Studio C ++とGCCの両方がサポートします)。
// pass number of arguments version
#define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}
// NULL terminated array version
#define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}
あなたが書いた典型的な可変機能を持っているとしましょう。可変引数の1つ ...
の前に少なくとも1つの引数が必要であるため、使用時に常に追加の引数を記述する必要があります。
またはあなたですか?
可変長関数をマクロでラップする場合、先行する引数は不要です。この例を考えてみましょう:
#define LOGI(...)
((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
毎回初期引数を指定する必要がないため、これは明らかにはるかに便利です。
これがすべてのコンパイラで機能するかどうかはわかりませんが、今のところ機能しています。
void inner_func(int &i)
{
va_list vars;
va_start(vars, i);
int j = va_arg(vars);
va_end(vars); // Generally useless, but should be included.
}
void func(int i, ...)
{
inner_func(i);
}
必要に応じて、inner_func()に...を追加できますが、必要ありません。 va_startは、指定された変数のアドレスを開始点として使用するため機能します。この場合、func()の変数への参照を提供しています。そのため、そのアドレスを使用し、その後スタック上の変数を読み取ります。 inner_func()関数はfunc()のスタックアドレスから読み取りを行っています。したがって、両方の関数が同じスタックセグメントを使用する場合にのみ機能します。
開始点としてvarを指定すると、va_startマクロとva_argマクロは通常機能します。必要に応じて、他の関数にポインターを渡し、それらも使用できます。独自のマクロを簡単に作成できます。すべてのマクロは、メモリアドレスを型キャストします。しかし、それらをすべてのコンパイラーで機能させ、呼び出し規約を作成するのは面倒です。そのため、一般的にコンパイラに付属しているものを使用する方が簡単です。