可変数の引数を渡す


333

可変数の引数をとる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);
 }

4
あなたの例は、fmtをformat_string()とfprintf()の両方に渡すという点で、少し奇妙に見えます。format_string()はどういうわけか新しい文字列を返す必要がありますか?
クリストファージョンソン

2
例は意味がありません。コードの概要を示すだけでした。
Vicentマルティ

162
「ググる必要があります」:同意しません。Googleには多くのノイズがあります(不明確で、しばしば混乱する情報)。Stackoverflowで良い(投票された、受け入れられた回答)ことは本当に役立ちます!
Ansgar

71
比較検討するために、私はグーグルからこの質問に行きました、そしてそれはスタックオーバーフローだったので、答えが役に立つだろうと非常に確信していました。だから聞いてください!
tenpn 2009

32
@Ilya:誰もGoogle以外で書き留めたことがなければ、Googleで検索する情報はありません。
エリックカプルン2012

回答:


211

楕円を渡すには、楕円をva_listに変換し、そのva_listを2番目の関数で使用する必要があります。具体的には;

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

3
コードは質問から取られたものであり、実際に機能するものではなく、楕円を変換する方法の単なる実例です。あなたがそれを見ると、format_stringfmtをその場で変更しなければならないので、どちらもほとんど役に立たないでしょう。オプションには、format_stringを完全に削除してvfprintfを使用することが含まれますが、これは、format_stringが実際に何を行うか、またはformat_stringが別の文字列を返すようにすることを想定しています。後者を表示するように回答を編集します。
SmacL 2012

1
フォーマット文字列が偶然printfと同じフォーマット文字列コマンドを使用している場合、フォーマット文字列が渡された実際の引数と互換性がない場合、gccやclangなどのコンパイラーで警告を表示することもできます。GCC関数属性 '詳細については、「フォーマット」:gcc.gnu.org/onlinedocs/gcc/Function-Attributes.htmlをご覧ください。
Doug Richardson

1
引数を2回続けて渡した場合、これは機能しないようです。
fotanus

2
@fotanus:で関数を呼び出しargptr、呼び出された関数がを使用するargptr場合、安全な唯一のことは、呼び出しva_end()てから再起動va_start(argptr, fmt);して再初期化することです。またはva_copy()、システムでサポートされている場合に使用できます(C99およびC11では必要ですが、C89 / 90では必要ありません)。
ジョナサンレフラー2015年

1
@ ThomasPadron-McCarthyのコメントは古くなり、最終的なfprintfは問題ないことに注意してください。
フレデリック

59

いたずらで移植性のないトリックを使いたくない場合を除き、それに渡す引数の数を知らずに(たとえば)printfを呼び出す方法はありません。

一般的に使用される溶液は、常に可変引数関数の代替形態を提供することであり、そうprintf有するvprintfとるva_listの代わりに......バージョンは周りだけのラッパーですva_listバージョン。


注意vsyslogされていない POSIXの準拠しています。
patryk.beza

53

可変関数危険な場合があります。これはより安全なトリックです:

   void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }

func((type[]){val1,val2,val3,val4,0});

11
さらに良いことに、このトリックがある: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
リチャード・J・ロスIII

5
@ RichardJ.RossIIIコメントを拡張してほしいのですが、このように読むことはほとんど不可能です。コードの背後にあるアイデアを理解することはできません。実際、非常に興味深く、有用に見えます。
ペネロペ2012年

5
@ArtOfWarfare私はそれが悪いハックであることに同意しません。Roseには素晴らしい解決策がありますが、func((type []){val1、val2、0});と入力する必要があります。#define func_short_cut(...)func((type []){ VA_ARGS }); があった場合、これは不格好な感じです。その後、単にfunc_short_cut(1、2、3、4、0);を呼び出すことができます。これにより、通常の可変長関数と同じ構文が得られますが、Roseの巧妙なトリックの利点が追加されています...ここの問題は何ですか?
chrispepper1989

9
引数として0を渡したい場合はどうなりますか?
ジュリアンゴールド

1
これは、ユーザーが終端の0で呼び出すことを覚えておく必要があります。
cp.engr 2016

29

壮大な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...);
}

可変テンプレートがまだVisual Studioで利用できないことを忘れないでください...もちろん、これはあなたにはまったく問題ではないかもしれません!
Tom Swirly、2013

1
Visual Studioを使用している場合は、2012年11月のCTPを使用して、可変テンプレートをVisual Studio 2012に追加できます。Visual Studio 2013を使用している場合は、可変テンプレートが使用されます。
user2023370 2013

7

関数呼び出しにはインラインアセンブリを使用できます。(このコードでは、引数は文字であると想定しています)。

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

4
移植性がなく、コンパイラに依存し、コンパイラの最適化を妨げます。非常に悪い解決策。
Geoffroy、

4
削除せずに新しい。
user7116 2013

8
少なくともこれは、質問を再定義することなく、実際に質問に答えます。
lama12345 2016年

6

マクロも試すことができます。

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

6

最初にフォーマッターをローカルバッファーに格納することで問題を解決できますが、スタックが必要で、対処する必要がある場合があります。以下を試してみましたが、うまく動作しているようです。

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

お役に立てれば。


2

ロスの解決策は少し片付けました。すべての引数がポインターである場合にのみ機能します。また、言語の実装__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);}

0

あなたが書いた典型的な可変関数があるとしましょう。可変引数の前に少なくとも1つの引数が必要であるため、使用時には...常に追加の引数を記述する必要があります。

それともあなたは?

可変個関数をマクロでラップする場合、先行する引数は必要ありません。この例を考えてみましょう:

#define LOGI(...)
    ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

毎回初期引数を指定する必要がないため、これは明らかにはるかに便利です。


-5

これがすべてのコンパイラで機能するかどうかはわかりませんが、これまでのところ機能しています。

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()のスタックアドレスから読み取っています。したがって、両方の関数が同じスタックセグメントを使用する場合にのみ機能します。

va_startマクロとva_argマクロは、開始点としてvarを指定すると、通常は機能します。したがって、必要に応じて他の関数にポインタを渡し、それらも使用できます。独自のマクロを簡単に作成できます。マクロが行うことはすべて、メモリアドレスを型キャストすることです。ただし、すべてのコンパイラと呼び出し規約でこれらを機能させるのは面倒です。したがって、コンパイラに付属しているものを使用する方が一般に簡単です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.