純粋な標準 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' のサイズが負です
$