C / C ++でのコンパイル時検証

C / C ++では、プログラムのコンパイル段階で定数式をチェックできます。 これは、将来コードを変更する際の問題を回避する安価な方法です。

私はと働くことを検討します:







BOOST_STATIC_ASSERTおよびall-all-all



コンパイル時にコンパイラを壊す方法たくさんあります 。 これらのうち、私は何よりもパフォーマンスが気に入っています。

#define ASSERT(cond) typedef int foo[(cond) ? 1 : -1]
      
      







ただし、プログラムでブーストを使用する場合、 BOOST_STATIC_ASSERTのようなものを発明する必要はありません。 サポートは、C ++ 11(static_assert)で行われること約束しています



ツールを整理し、使用方法について説明します。



列挙型の要素の数を制御する



列挙は、意味によって関連付けられた定数のセットであり、通常はプログラムロジックの分岐点で使用されます。 通常、分岐点がいくつかあり、何かを簡単にスキップできます。

例:

 enum TEncryptMode { EM_None = 0, EM_AES128, EM_AES256, <b>EM_ItemsCount</b> };
      
      





最後の要素はアルゴリズムではなく、最大セマンティック要素よりも1大きい補助定数です。



これで、このセットの定数が使用される場合はいつでも、チェックを追加するだけです。

 ASSERT(EM_ItemsCount == 3);
      
      





将来新しい定数が追加されると、この時点でコードはコンパイルを停止します。 そのため、変更の作成者はコードのこのセクションを確認し、必要に応じて新しい定数を考慮する必要があります。



EM_ItemsCountの導入からのボーナスとして、関数パラメーターのランタイムチェックを挿入することが可能になります。

 assert( 0 <= mode && mode < EM_ItemsCount );
      
      





そのような定数のないオプションと比較してください。

 assert( 0 <= mode && mode <= EM_AES256 );
      
      





(EM_AES512を追加して、間違ったチェックを取得します)



配列と列挙



前のセクションからの検証の特殊なケース。

同じ暗号化アルゴリズムのパラメーターを持つ配列があるとします(この例は指から少し吸い上げられていますが、実際には同様のケースがあります)。

 static const ParamStruct params[] = { { EM_None, 0, ... }, { EM_AES128, 128, ... }, { EM_AES256, 256, ... }, { -1, 0, ... } };
      
      





この構造をTEncryptModeと同期させる必要があります。

(なぜ配列の最後の要素が必要なのか、説明する必要はないと思います。)



配列の長さを計算するための補助マクロが必要です。

 #define lengthof(x) (sizeof(x) / sizeof((x)[0]))
      
      





これで、チェックを書くことができます(params定義の直後が良い場合):

 ASSERT( lengthof(params) == EM_ItemsCount + 1 );
      
      





upd: コメントでは、 skor habrayuzerはlengthofマクロのより安全なバージョンを提案しました。



スイッチ



ここですべてが明らかです(上記の例の後)。 スイッチ(モード)を追加する前に:

 ASSERT(EM_ItemsCount == 3);
      
      







少しわかりにくいランタイムチェック:

 ASSERT(EM_ItemsCount == 3); switch( mode ) { case ...: ... break; ... <b>default: assert( false );</b> }
      
      





ミスに対する防御のための追加の要塞。 アクションが同じ方法で処理される場合、1つのアクションに対して複数のケース条件をリストし、デフォルトを空のままにしておくことをお勧めします。

 ... case ET_AES128: case ET_AES256: ... break; ...
      
      







異種データを持つクラス



enum'ovから脱線して、そのようなクラスを見てみましょう。

 class MyData { ... private: int a; double b; ... };
      
      







将来、誰かがint c変数を追加したいと思うかもしれません。 この時までにクラスは大きく複雑になりました。 変数cを書き込むポイントを見つける方法は?



このような半自動解法が提案されています-クラスにデータバージョン定数定数を設定します。

 class MyData { static const int DataVersion = 0; ... };
      
      





これで、すべてのデータの整合性を追跡することが重要なすべての方法で、次のように記述できます。

 ASSERT(DataVersion == 0);
      
      







クラスに新しいデータを追加するには、DataVersion定数を手動で増やす必要があります(ここでは、規律が必要です)。 ただし、コンパイラはチェックする必要のある場所にすぐに注意を払います。 これらの検証ポイントには次のものが含まれます。



残りの検証場所は、内部ロジック(たとえば、ログへの出力)に依存します。

データをディスクに保存するときに同じ定数(DataVersion)を使用すると便利です(興味がある場合は、個別に記述できます)。



ベネフィット



結果は何ですか?

長所:





短所:





私にとって、利点はそれを上回りますが、あなたにとっては?



updコードの強調表示を追加しました。



All Articles