static_assert
あたらしい C 言語規格 C1X では、コンパイル時に評価可能な静的表明をソースコードに埋めておけるらしい。 うらやましい。
https://www.securecoding.cert.org/confluence/display/seccode/DCL03-C.+Use+a+static+assertion+to+test+the+value+of+a+constant+expression
この静的表明は、構造体メンバーのオフセットや、文字列定数の長さをの確認をコンパイル時におこなうもの。
今回わたしが突き当たった問題は、以下のようなソースコードを移植ターゲットコンパイラーに通した時に、配列サイズの計算を(コンパイラーが)間違える可能性があるという警告だった。
#define L_DATA "\x32\xab\xc3\x00..." ... char data_buf[sizeof(L_DATA) - 1];
仕方なく L_DATA に含まれる要素数をぽちぽち数えて L_DATA_LEN 定数を define で宣言したのだけれど、どうにも不安。 わたしは要素数を間違いなく数え上げただろうか? 別の保守者が L_DATA を書き換えた時に、隣に宣言した L_DATA_LEN をちゃんと更新してくれるだろうか…?
static assert はこういうときに使える。
#define L_DATA "..." #define L_DATA_LEN 128 static_assert(L_DATA_LEN == sizeof(L_DATA) - 1); ... char data_buf[L_DATA_LEN];
もし L_DATA と L_DATA_LEN に不整合があれば、これをコンパイル時に検出してもらえるわけだ。
C89/C99 で使える static assert
数年前、 (尊敬する元) 同僚から素の C 言語で使える static assert を教えてもらったことがあった。 それを参考に、もう少し記述量を減らした版をメモしておく。
#define CONCAT(a,b) CONCAT_(a,b) #define CONCAT_(a,b) a##b #define STATIC_ASSERT(cond)\ enum { CONCAT(kStaticAssert,__LINE__) =\ sizeof(struct { int assertion_failed[(cond) ? 1 : -1]; }) }
肝は STATIC_ASSERT マクロ内の無名構造体メンバー assertion_failed の配列長。 「コンパイル時」に確定できる条件をみて、成立すれば 1、しなければ -1 にしている。
配列長 -1 は規格で禁止されているので、条件に抵触すればコンパイルエラーが起きて間違いに気づける。
STATIC_ASSERT の上部構造の無名列挙は、その翻訳単位内で唯一となる列挙子名をひねり出すために使っている。 唯一性は行番号 __LINE__ に寄っていて、これを kStaticAssert という前置詞と連結するために CONCAT および CONCAT_ マクロがある。
補足: 簡略版 static assert
使用可能範囲が関数ブロック内部でだけでよいなら、もっと簡単に定義できる。
#define STATIC_ASSERT(cond)\ do { int assertion_failed[(cond) ? 1 : -1];\ (void) assertion_failed}); } while(0)
do ... while(0) 内の名前 assertion_failed はブロック内でのみ有効なので、 CONCAT マクロで都度生成しなくても名前は衝突しない。
なお、先の翻訳単位内で(おおむねどこでも)使える版も、その翻訳単位内で一度だけ使えればよいなら CONCAT を省略できる。 こちらならコンパイル時のエラー検出の方法がもう少し把握しやすいだろう。
#define STATIC_ASSERT(cond)\ enum { kStaticAssert_ =\ sizeof(struct { int assertion_failed[(cond) ? 1 : -1]; }) }