10

任意の型で動作する C マクロが必要だとしましょう。GCC コンパイラ (>= 4.6) を使用しており、GNU99 マクロを使用できます。

//code...
any_type_t *retVal = function_that_runs_very_long_time(a, b, &&c, **d, &e, *f);
//other code...

TIMER のマクロの使用法は、たとえば次のようになります。

//code...
any_type_t *retVal = 
    TIMER(
          function_that_runs_very_long_time(a, b, &&c, **d, &e, *f),
          "TIMING FOR VALUE <%d, %d>", a, b
         );
//other code...

そのため、TIMER は指定された関数の値を返し、その実行時間を出力する必要があります。void戻り型を持つ関数に問題があります。

明らかに TIMER_TYPE と TIMER_VOID のような 2 つのマクロを使用できますが、任意の戻り値で関数を時間指定する単一の関数を使用したいと考えています。

提案していただきありがとうございます。


この TIMER マクロの編集例

#define TIMER(expr, fmt_msg, ...)                           \
({                                                          \
    struct timeval before, after;                           \
    uint64_t time_span;                                     \
    int time_span_sec, time_span_usec;                      \
    gettimeofday(&before, NULL);                            \
    typeof(expr) _timer_expr__ = (expr);                    \ // <- static if?
    gettimeofday(&after, NULL);                             \
    time_span = (after.tv_sec * 1000000 + after.tv_usec)    \
              - (before.tv_sec * 1000000 + before.tv_usec); \
    time_span_sec  = time_span / 1000000;                   \
    time_span_usec = time_span % 1000000;                   \
    TRACE(fmt_msg "\n%s : %d.%d seconds",                   \
          #expr, time_span_sec, time_span_usec, ...);       \
    _timer_expr__;                                          \
})
4

4 に答える 4

12

なんと興味深い質問でしょう。

いくつかの実験の後、GCC の組み込み関数__builtin_types_compatible_pを使用するソリューションを見つけました。__builtin_choose_expr

__builtin_types_compatible_p

GCCマニュアルの引用:

組み込み関数:int __builtin_types_compatible_p (type1, type2)

組み込み関数__builtin_types_compatible_pを使用して、2 つの型が同じかどうかを判断できます。

この組み込み関数は1、非修飾バージョンの型type1and type2(式ではなく型) に互換性があるかどうかを返し、そうでない場合に返します0この組み込み関数の結果は、整数定数式で使用できます。

この組み込み関数は、最上位修飾子 ( ,constなどvolatile) を無視します。たとえば、intは と同等const intです。

void「ネス」をチェックする方法は次のとおりです。

#define __type_is_void(expr) __builtin_types_compatible_p(typeof(expr), void)

__builtin_choose_expr

組み込み関数:type __builtin_choose_expr (const_exp, exp1, exp2)

組み込み関数__builtin_choose_exprを使用して、定数式の値に応じてコードを評価できます。この組み込み関数は、整数定数式である がゼロ以外のexp1場合に戻ります。const_expそれ以外の場合は を返しますexp2

? :この組み込み関数は、C の演算子に似ていますが、返される式の型がプロモーション ルールによって変更されない点が異なります。また、組み込み関数は、選択されていない式を評価しません。たとえば、ifconst_expが true と評価された場合、exp2副作用があっても評価されません。

が返された場合exp1、戻り値の型は の型と同じexp1です。同様に、exp2が返される場合、その戻り値の型は と同じexp2です。

したがって、__builtin_choose_expr組み込みは、コンパイル時に評価される「静的スイッチ」のようなものです。

準備

ここにマクロを貼り付けませんTIMERが、2 つのバージョンに分割できると思います。1 つは voidexpr用で、もう 1 つは残り用です。以下は、式を評価して同じ型の結果を生成するスタブです。

#define __DO(expr) \
    ({ typeof(expr) __ret; __ret = (expr); __ret; })

#define __DO_VOID(expr) \
    (void) (expr)

素朴な解決策

これで、式の実際の型に応じて、2 つの実装を静的に切り替えることができます。しかし、実際には単純な解決策は機能しません。以下を参照してください。

#define DO(expr) \
    __builtin_choose_expr(__type_is_void(expr), \
        __DO_VOID(expr), \
        __DO(expr))  # won't work

void 式を渡してこのコードをコンパイルしようとすると、次のエラーが発生します。

test.c:28:9: error: variable or field ‘__ret’ declared void
test.c:28:9: error: void value not ignored as it ought to be

__DO_VOIDが選択されていますが、__DOエラーが発生します。この動作はマニュアルに記載されています:

... 未使用の式 (exp1またはexp2の値に応じてconst_exp) は、依然として構文エラーを生成する可能性があります。これは、将来の改訂で変更される可能性があります。

ワーキングソリューション

秘訣は、元の voidexprを void 以外の値に置き換えて、ケースをコンパイルできるようにすること__DOです (これはとにかく void の場合はデッド コードですexpr)。

#define __expr_or_zero(expr) __builtin_choose_expr(__type_is_void(expr), 0, (expr))

#define DO(expr) \
    __builtin_choose_expr(__type_is_void(expr), \
        __DO_VOID(expr), \
        __DO(__expr_or_zero(expr))) # works fine!

それでおしまい!Ideone の完全なソース コードは次のとおりです: http://ideone.com/EFy4pE

于 2013-03-16T11:37:26.287 に答える
2

「これは本当にありえない」という答えを受け入れることができますか?

マクロから戻る部分ではありません。しかし、戻り値の型について expr を条件付きでテストすることに関する部分。

実際には、次のようなものを求めています。

「is_expr_type_void(expr)」と呼ばれる魔法のチェックの代わりに、次のマクロのバリエーションで is_void または !is_void を示す呼び出し時に 1 または 0 を渡すだけです。

#define TIMER(is_void, expr, fmt_msg, ...)                  \
({                                                          \
    struct timeval before, after;                           \
    uint64_t time_span;                                     \
    int time_span_sec, time_span_usec;                      \
    gettimeofday(&before, NULL);                            \
    if (is_void)                                            \
        (expr)                                              \
    else                                                    \
        typeof(expr) _timer_expr__ = (expr);                \ // <- static if?
    gettimeofday(&after, NULL);                             \
    time_span = (after.tv_sec * 1000000 + after.tv_usec)    \
              - (before.tv_sec * 1000000 + before.tv_usec); \
    time_span_sec  = time_span / 1000000;                   \
    time_span_usec = time_span % 1000000;                   \
    TRACE(fmt_msg "\n%s : %d.%d seconds",                   \
          #expr, time_span_sec, time_span_usec, ...);       \
    if (!is_void)                                           \
        _timer_expr__;                                      \
})

これは単に機能しません。プリプロセッサは、void 関数呼び出しと非 void 関数呼び出しの両方の場合に、if-else 条件のコードを作成します。両方の側は、非 void 関数に対して正常にコンパイルされます。しかし、コンパイラは、TIMER が void 関数で呼び出されると、条件の「else」部分で常にチョークします…コードが呼び出されることはないという事実にもかかわらず。

(今、それがデッドコードであることを識別し、コンパイル時エラーとしてフラグを立てる前にデッドストリップすることができる本当にスマートなコンパイラが存在する場合、あなたは運がいいでしょう!しかし、gcc 4.6はそうではないと思います.その賢い…)

これにより、#define 内の #if (is_void) 条件の優先オプションとなるものが残ります。しかし、それは単に許可されていません。この回答が条件付き前処理に関する同様の質問に答えようとして指摘しているように、プリプロセッサはturing-completeではありません。

だから…単一のマクロを持ちたいというあなたの願望にもかかわらず、あなたの最も簡単な答えは、void関数用に1つ、戻り値を持つ関数用に1つ作成することだと思います。

于 2012-09-20T05:25:28.510 に答える
0

本当にマクロから戻る必要がある場合は、代わりにインライン関数を使用してください。

于 2012-09-19T11:51:53.890 に答える