47

エラーの場合、実行時に失敗するのではなく、コンパイルを防ぐ「アサート」を実装したいと思います。

現在、このように定義されたものがあります。これはうまく機能しますが、バイナリのサイズが大きくなります。

#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;}

サンプル コード (コンパイルに失敗します)。

#define DEFINE_A 1
#define DEFINE_B 1
MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B);

コードを生成しないようにこれを実装するにはどうすればよいですか (生成されるバイナリのサイズを最小限に抑えるため)。

4

10 に答える 10

50

純粋な標準 C でのコンパイル時のアサートは可能であり、少しのプリプロセッサのトリックにより、その使用法は の実行時の使用法と同じくらいきれいに見えますassert()

重要なトリックは、コンパイル時に評価でき、一部の値でエラーを引き起こす可能性のある構造を見つけることです。答えの 1 つは、配列の宣言に負のサイズを指定できないことです。typedef を使用すると、成功時にスペースが割り当てられなくなり、失敗時にエラーが保持されます。

エラー メッセージ自体は、負のサイズの宣言を暗示的に参照するため (GCC では「配列 foo のサイズは負です」と表示されます)、このエラーが実際にアサーション チェックであることを示唆する配列型の名前を選択する必要があります。

さらに対処すべき問題はtypedef、特定の型名はコンパイル単位で 1 回しか使用できないということです。したがって、マクロは、宣言する一意の型名を取得するために、使用ごとに調整する必要があります。

私の通常の解決策は、マクロが 2 つのパラメーターを持つことを要求することでした。1 つ目は真であることをアサートする条件で、2 つ目はバックグラウンドで宣言された型名の一部です。台座による答えは、トークンの貼り付けと__LINE__事前定義されたマクロを使用して、おそらく追加の引数を必要とせずに一意の名前を形成することを示唆しています。

残念ながら、アサーション チェックがインクルード ファイルにある場合でも、2 番目のインクルード ファイルの同じ行番号、またはメイン ソース ファイルのその行番号のチェックと競合する可能性があります。マクロを使用してそれを上書きすることもでき__FILE__ますが、これは文字列定数として定義されており、文字列定数を識別子名の一部に戻すことができるプリプロセッサのトリックはありません。正当なファイル名には、識別子の正当な部分ではない文字を含めることができることは言うまでもありません。

したがって、次のコード フラグメントを提案します。

/** A compile time assertion check.
 *
 *  Validate at compile time that the predicate is true without
 *  generating code. This can be used at any point in a source file
 *  where typedef is legal.
 *
 *  On success, compilation proceeds normally.
 *
 *  On failure, attempts to typedef an array type of negative size. The
 *  offending line will look like
 *      typedef assertion_failed_file_h_42[-1]
 *  where file is the content of the second parameter which should
 *  typically be related in some obvious way to the containing file
 *  name, 42 is the line number in the file on which the assertion
 *  appears, and -1 is the result of a calculation based on the
 *  predicate failing.
 *
 *  \param predicate The predicate to test. It must evaluate to
 *  something that can be coerced to a normal C boolean.
 *
 *  \param file A sequence of legal identifier characters that should
 *  uniquely identify the source file in which this condition appears.
 */
#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file)

#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line, file) \
    typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1];

典型的な使用法は次のようなものです。

#include "CAssert.h"
...
struct foo { 
    ...  /* 76 bytes of members */
};
CASSERT(sizeof(struct foo) == 76, demo_c);

GCC では、アサーションの失敗は次のようになります。

$ gcc -c demo.c
demo.c:32: エラー: 配列 `assertion_failed_demo_c_32' のサイズが負です
$
于 2009-04-30T22:41:46.383 に答える
8

次のCOMPILER_VERIFY(exp)マクロはかなりうまく機能します。

// 引数を結合します (引数を展開した後)
#define GLUE(a,b) __GLUE(a,b)
#define __GLUE(a,b) a ## b

#define CVERIFY(expr, msg) typedef char GLUE (compiler_verify_, msg) [(expr) ? (+1) : (-1)]

#define COMPILER_VERIFY(exp) CVERIFY (exp, __LINE__)

これは C と C++ の両方で機能し、typedef が許可される場所ならどこでも使用できます。式が真の場合、1 文字の配列の typedef が生成されます (これは無害です)。式が false の場合、-1 文字の配列の typedef が生成され、通常はエラー メッセージが表示されます。引数として与えられる式は、コンパイル時の定数に評価されるものなら何でもかまいません (したがって、sizeof() を含む式は問題なく動作します)。これにより、より柔軟になります。

#if (式)
#エラー
#endif

ここでは、プリプロセッサによって評価できる式に制限されています。

于 2009-04-30T15:43:47.403 に答える
4

Cの静的アサーションで見つけた最高の記事はpixelbeatです。静的アサーションがC++0Xに追加され、C1Xに組み込まれる可能性があることに注意してください。ただし、しばらくはそうはなりません。私が提供したリンクのマクロがバイナリのサイズを大きくするかどうかはわかりません。少なくとも、妥当なレベルの最適化でコンパイルした場合は、そうはならないと思いますが、マイレージは異なる場合があります。

于 2009-04-30T15:07:26.803 に答える
4

コンパイラが DEBUG や NDEBUG などのプリプロセッサ マクロを設定する場合、次のようなものを作成できます (そうでない場合は、Makefile でこれを設定できます)。

#ifdef DEBUG
#define MY_COMPILER_ASSERT(EXPRESSION)   switch (0) {case 0: case (EXPRESSION):;}
#else
#define MY_COMPILER_ASSERT(EXPRESSION)
#endif

次に、コンパイラはデバッグ ビルドに対してのみアサートします。

于 2009-04-30T14:47:49.753 に答える
3

あなたがCに興味を持っていることは知っていますが、boostのC++ static_assertを見てください。(ちなみに、これはC ++ 1xで利用可能になる可能性があります。)

これもC++で同様のことを行いました。

#define COMPILER_ASSERT(expr)enum {ARG_JOIN(CompilerAssertAtLine、__LINE__)= sizeof(char [(expr)?+1:-1])}

どうやら、これはC++でのみ機能します。 この記事では、Cで使用するためにそれを変更する方法について説明します。

于 2009-04-30T15:09:15.117 に答える
3

'#error' の使用は、ほとんどのコンパイラでコンパイルを停止させる有効なプリプロセッサ定義です。たとえば、デバッグでのコンパイルを防ぐために、次のようにすることができます。


#ifdef DEBUG
#error Please don't compile now
#endif
于 2009-04-30T14:51:26.640 に答える
2

最終的なバイナリをコンパイルするときは、MY_COMPILER_ASSERT を空白に定義して、その出力が結果に含まれないようにします。デバッグのために持っている方法でのみ定義してください。

しかし実際には、この方法ですべてのアサーションをキャッチできるわけではありません。コンパイル時に意味をなさないものもあります (値が null ではないというアサーションなど)。できることは、他の #defines の値を確認することだけです。なぜあなたがそれをしたいのかよくわかりません。

于 2009-04-30T14:43:28.780 に答える
-2

さて、あなたはstatic assertsブーストライブラリでを使うことができます。

彼らがそこで行っていると私が信じているのは、配列を定義することです。

 #define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)];

EXPRESSIONがtrueの場合、char x[1];それはOKを定義します。falseの場合、char x[0];どちらが違法であるかを定義します。

于 2009-04-30T15:08:25.820 に答える