158

C99 の可変個引数マクロの空の引数には、よく知られた 問題があります。

例:

#define FOO(...)       printf(__VA_ARGS__)
#define BAR(fmt, ...)  printf(fmt, __VA_ARGS__)

FOO("this works fine");
BAR("this breaks!");

上記の使用はBAR()、C99 標準によれば、次のように展開されるため、実際には正しくありません。

printf("this breaks!",);

末尾のコンマに注意してください - 機能しません。

一部のコンパイラ (例: Visual Studio 2010) は、末尾のコンマを静かに取り除きます。他のコンパイラ (例: GCC) は、次のよう##に の前に置くことをサポートしています:__VA_ARGS__

#define BAR(fmt, ...)  printf(fmt, ##__VA_ARGS__)

しかし、この動作を取得するための標準に準拠した方法はありますか? おそらく複数のマクロを使用していますか?

現在、この##バージョンは (少なくとも私のプラットフォームでは) 十分にサポートされているように見えますが、私はむしろ標準に準拠したソリューションを使用したいと考えています。

プリエンプティブ: 小さな関数を書けることはわかっています。マクロを使用してこれを実行しようとしています。

編集: BAR() を使用する理由の例 (単純ですが) を次に示します。

#define BAR(fmt, ...)  printf(fmt "\n", ##__VA_ARGS__)

BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);

fmtこれにより、常に二重引用符で囲まれた C 文字列であると仮定して、BAR() ログ ステートメントに改行が自動的に追加されます。改行を個別の printf() として出力しません。これは、ロギングが行バッファリングされ、複数のソースから非同期的に来る場合に有利です。

4

12 に答える 12

116

使用できる引数カウントのトリックがあります。

BAR()jwd の質問の 2 番目の例を実装する標準準拠の方法の 1 つを次に示します。

#include <stdio.h>

#define BAR(...) printf(FIRST(__VA_ARGS__) "\n" REST(__VA_ARGS__))

/* expands to the first argument */
#define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway)
#define FIRST_HELPER(first, ...) first

/*
 * if there's only one argument, expands to nothing.  if there is more
 * than one argument, expands to a comma followed by everything but
 * the first argument.  only supports up to 9 arguments but can be
 * trivially expanded.
 */
#define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
#define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__)
#define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__)
#define REST_HELPER_ONE(first)
#define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__
#define NUM(...) \
    SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
#define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10

int
main(int argc, char *argv[])
{
    BAR("first test");
    BAR("second test: %s", "a string");
    return 0;
}

この同じトリックは、次の目的で使用されます。

説明

戦略は__VA_ARGS__、最初の引数と残り (もしあれば) に分けることです。これにより、最初の引数の後、2 番目の引数 (存在する場合) の前に何かを挿入することが可能になります。

FIRST()

このマクロは単純に最初の引数に展開され、残りは破棄されます。

実装は簡単です。このthrowaway引数により、 は 2 つの引数を確実に取得します。これは、が少なくとも 1 つ必要FIRST_HELPER()なため必要です。...引数が 1 つの場合、次のように展開されます。

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

2 つ以上の場合、次のように展開されます。

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. firstarg

REST()

このマクロは、最初の引数以外のすべてに展開されます (複数の引数がある場合は、最初の引数の後のコンマを含みます)。

このマクロの実装は、はるかに複雑です。一般的な戦略は、引数の数 (1 つまたは複数) をカウントし、(引数が 1 つREST_HELPER_ONE()だけの場合) またはREST_HELPER_TWOORMORE()(2 つ以上の引数が指定された場合) のいずれかに拡張することです。 REST_HELPER_ONE()最初の引数の後に引数がないため、残りの引数は空のセットになります。 REST_HELPER_TWOORMORE()これも簡単です。コンマに展開され、その後に最初の引数以外のすべてが続きます。

引数は、NUM()マクロを使用してカウントされます。このマクロはONE、引数が 1 つだけのTWOORMORE場合、2 つから 9 つの引数が指定された場合に展開され、10 個以上の引数が指定された場合にブレークします (10 番目の引数に展開されるため)。

NUM()マクロは、マクロを使用してSELECT_10TH()引数の数を決定します。その名前が示すように、SELECT_10TH()単純に 10 番目の引数に展開されます。省略記号のため、SELECT_10TH()少なくとも 11 個の引数を渡す必要があります (標準では、省略記号には少なくとも 1 つの引数が必要であると規定されています)。これが、 が最後の引数としてNUM()渡される理由です(これがないと、 に引数を 1 つ渡すと、に 10 個の引数しか渡されず、標準に違反します)。throwawayNUM()SELECT_10TH()

またはのいずれかの選択はREST_HELPER_ONE()、inの展開とREST_HELPER_TWOORMORE()連結することによって行われます。の目的は、と連結される前に が完全に展開されるようにすることであることに注意してください。REST_HELPER_NUM(__VA_ARGS__)REST_HELPER2()REST_HELPER()NUM(__VA_ARGS__)REST_HELPER_

引数が 1 つの展開は次のようになります。

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (空の)

2 つ以上の引数を使用した展開は、次のようになります。

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , secondarg, thirdarg
于 2012-06-23T20:19:19.770 に答える
69

Richard Hansen の answer to this question,##__VA_ARGS__で説明されているように、可変引数マクロに渡すことができる引数の数にハードコードされた上限を受け入れる場合は、GCC の拡張機能の使用を避けることができます。ただし、そのような制限を設けたくない場合は、私の知る限り、C99 で指定されたプリプロセッサ機能だけを使用することはできません。言語に何らかの拡張機能を使用する必要があります。clang と icc はこの GCC 拡張機能を採用していますが、MSVC は採用していません。

__VA_ARGS__2001 年に、標準化のための GCC 拡張機能 (およびrest-parameter以外の名前を使用できるようにする関連拡張機能) をドキュメント N976に書きましたが、委員会からは何の反応もありませんでした。読んだ人がいるかどうかもわかりません。2016 年にN2023で再び提案されました。その提案がどのように行われるかを知っている人は、コメントでお知らせください。

于 2011-04-08T01:08:09.800 に答える
18

一般的な解決策ではありませんが、printf の場合、次のように改行を追加できます。

#define BAR_HELPER(fmt, ...) printf(fmt "\n%s", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, "")

フォーマット文字列で参照されていない余分な引数は無視されると思います。したがって、おそらく次の方法でも回避できます。

#define BAR_HELPER(fmt, ...) printf(fmt "\n", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, 0)

これを行うための標準的な方法がなければ、C99 が承認されたとは信じられません。AFAICT 問題は C++11 にも存在します。

于 2011-12-29T21:48:19.473 に答える
5

私は最近同様の問題に遭遇しましたが、解決策があると信じています。

NUM_ARGS重要なアイデアは、可変引数マクロに与えられた引数の数をカウントするマクロを作成する方法があるということです。NUM_ARGSbuildのバリエーションを使用できますNUM_ARGS_CEILING2。これにより、可変個引数マクロに 1 つの引数が与えられているか、2 つ以上の引数が与えられているかがわかります。次に、2 つのヘルパー マクロのうちの 1 つにその引数を送信するようにBarマクロを作成できます。NUM_ARGS_CEILING2CONCAT

このトリックを使用してマクロを作成する例を次に示します。UNIMPLEMENTEDこれは、に非常によく似ていBARます。

ステップ1:

/** 
 * A variadic macro which counts the number of arguments which it is
 * passed. Or, more precisely, it counts the number of commas which it is
 * passed, plus one.
 *
 * Danger: It can't count higher than 20. If it's given 0 arguments, then it
 * will evaluate to 1, rather than to 0.
 */

#define NUM_ARGS(...)                                                   \
    NUM_ARGS_COUNTER(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13,       \
                     12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)    

#define NUM_ARGS_COUNTER(a1, a2, a3, a4, a5, a6, a7,        \
                         a8, a9, a10, a11, a12, a13,        \
                         a14, a15, a16, a17, a18, a19, a20, \
                         N, ...)                            \
    N

ステップ 1.5:

/*
 * A variant of NUM_ARGS that evaluates to 1 if given 1 or 0 args, or
 * evaluates to 2 if given more than 1 arg. Behavior is nasty and undefined if
 * it's given more than 20 args.
 */

#define NUM_ARGS_CEIL2(...)                                           \
    NUM_ARGS_COUNTER(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
                     2, 2, 2, 2, 2, 2, 2, 1)

ステップ2:

#define _UNIMPLEMENTED1(msg)                                        \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__)

#define _UNIMPLEMENTED2(msg, ...)                                   \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__, __VA_ARGS__)

ステップ 3:

#define UNIMPLEMENTED(...)                                              \
    CONCAT(_UNIMPLEMENTED, NUM_ARGS_CEIL2(__VA_ARGS__))(__VA_ARGS__)

CONCAT は通常の方法で実装されます。簡単なヒントとして、上記が紛らわしいと思われる場合: CONCAT の目的は、別のマクロ「呼び出し」に展開することです。

NUM_ARGS 自体は使用されないことに注意してください。ここで基本的なトリックを説明するためにそれを含めました。Jens Gustedt の P99 ブログを参照してください。

2 つの注意事項:

  • NUM_ARGS は、処理する引数の数が制限されています。数は完全に任意ですが、私の場合は最大 20 までしか処理できません。

  • 示されているように、NUM_ARGS には、引数が 0 の場合に 1 を返すという落とし穴があります。その要点は、NUM_ARGS が技術的には [コンマ + 1] をカウントしていて、引数ではないということです。この特定のケースでは、実際に有利に機能します。_UNIMPLEMENTED1 は空のトークンを問題なく処理し、_UNIMPLEMENTED0 を書き込む必要がなくなります。Gustedt にも回避策がありますが、私はそれを使用したことがなく、ここで行っていることで機能するかどうかはわかりません。

于 2012-06-22T19:25:27.193 に答える
3

これは私が使用する簡易版です。ここでの他の回答の優れたテクニックに基づいているため、それらには非常に多くの小道具があります。

#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX

#define _BAR_1(fmt)      printf(fmt "\n")
#define _BAR_N(fmt, ...) printf(fmt "\n", __VA_ARGS__);
#define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
    return 0;
}

それでおしまい。

他のソリューションと同様に、これはマクロの引数の数に制限されています。さらにサポートするには、 にパラメータを追加し_SELECT、さらにN引数を追加します。引数名は、カウントベースのSUFFIX引数が逆の順序で提供されることを思い出させるために、(アップではなく) カウントダウンします。

このソリューションは、0 個の引数を 1 個の引数として扱います。に展開されるため、名目BAR()上は「機能します」。_SELECT(_BAR,,N,N,N,N,1)()_BAR_1()()printf("\n")

必要に応じて、 を使用してクリエイティブになり、_SELECTさまざまな数の引数にさまざまなマクロを提供できます。たとえば、フォーマットの前に「レベル」引数を取る LOG マクロがあります。format が欠落している場合は、"(メッセージなし)" をログに記録します。引数が 1 つしかない場合は、"%s" を介してログに記録します。それ以外の場合は、format 引数を残りの引数の printf 形式文字列として扱います。

#define _LOG_1(lvl)          printf("[%s] (no message)\n", #lvl)
#define _LOG_2(lvl,fmt)      printf("[%s] %s\n", #lvl, fmt)
#define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "\n", #lvl, __VA_ARGS__)
#define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    LOG(INFO);
    LOG(DEBUG, "here is a log message");
    LOG(WARN, "here is a log message with param: %d", 42);
    return 0;
}
/* outputs:
[INFO] (no message)
[DEBUG] here is a log message
[WARN] here is a log message with param: 42
*/
于 2018-03-18T15:56:19.007 に答える
2

あなたの状況 (少なくとも 1 つの引数が存在し、0 ではない) では、 as を定義BARし、 Jens GustedtBAR(...)を使用してカンマを検出し、またはそれに応じてディスパッチできます。 HAS_COMMA(...)BAR0(Fmt)BAR1(Fmt,...)

これ:

#define HAS_COMMA(...) HAS_COMMA_16__(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)
#define HAS_COMMA_16__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define CAT_(X,Y) X##Y
#define CAT(X,Y) CAT_(X,Y)
#define BAR(.../*All*/) CAT(BAR,HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define BAR0(X) printf(X "\n")
#define BAR1(X,...) printf(X "\n",__VA_ARGS__)


#include <stdio.h>
int main()
{
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

-pedantic警告なしでコンパイルします。

于 2018-11-11T11:56:34.327 に答える
-2

標準的な解決策は、FOO代わりに を使用することですBAR__VA_ARGS__おそらくあなたにはできない引数の並べ替えのいくつかの奇妙なケースがあります(ただし、誰かがその中の引数の数に基づいて条件付きで逆アセンブルおよび再アセンブルする巧妙なハックを思いつくことができると思います!)しかし、一般的にFOOは「通常」を使用しますただ動作します。

于 2011-04-08T01:54:20.760 に答える