バグを回避するための10の主なルール-コメントで提案
この投稿は、今年6月のウェビナーでのスピーチで述べられたMichael Barrの著書「Embedded C Coding Standard」の重要な概念を無料で語ったものです。
一部のルールはC ++およびC拡張にのみ適用され、一部は言語標準に適用されます。
ルール1
中括弧{}常にif、else、switch、while、do、forステートメントの後にコードブロックをフレーム化する必要があります。
結果:単一のステートメントと空の式も中括弧で囲む必要があります。
証明:簡単なコード例を検討してください
if (5 == foo) bar(); always_run();
大丈夫のようで、すべてが明確になっていますが、一時的にbar関数の呼び出しを無効にすることにしました(たとえば、プログラムがクラッシュする場所を検索するとき)。
if (5 == foo) // bar(); always_run();
まったく予想外なことに、run関数はほとんど呼び出されなくなりました。括弧が適切に配置されていれば、これは起こりませんでした。
if (5 == foo) { // bar(); }; always_run();
ルールは明らかであるように見え、さまざまなソースで繰り返し見られますが、画面スペースは閉じ括弧で囲まれた空の行の下に置かれ、この場所はとても哀れなため、それに従うのはどれほど難しいですか? (トップ10に含まれていない別のルールに違反して、オペレーターに条件を追加し、行全体を保存できたことに注意してください)。 小さなコメント-単一の閉じ括弧を持つ行はコンパイルの意味で何の費用もかからないことを完全によく理解していますが、ご存知のように、私たちはコンピューターではなく他の人のためにプログラムを作成し、たとえば、できる限りコードが私にとって明確ですロールする必要なく、画面上のすべてを見ることができます。
ルール2
可能な限りconstキーワードを使用する
結果:初期化後に変更されない変数の説明で使用します。 変更してはならない関数パラメーターを説明する場合。 変更できない構造および関連付けのフィールドを記述する場合(デバイスレジスタを記述するための構造を含む)。 数値定数の#defineの推奨代替品として。
証明:このキーワードは実行中に費用がかかりません。一部のシステムでは、ROMに配置することでRAMを節約し、コンパイラーが無効なデータ操作を検出できるようにし、リーダーに追加情報を提供し、少しスペースを取ります(別の行は必要ありません)-つまり、ありませんこの規則を無視する理由./* 1 /
char const *gp_model_name = “Acme 9000”; int const g_build_number = CURRENT_BUILD_NUMBER(); char *strncpy (char *p_dest, char const *p_src, size_t count); size_t const HEAP_SIZE = 8192;
ルール3
可能な限り静的キーワードを使用してください。
結果:宣言するモジュールを除き、どこにも表示されるべきではないすべての非ローカル変数および関数に使用します。
証明:実行時にコストがかからず、カプセル化とデータ保護が向上し、長い寿命の変数をスペルミスから保護します。* 1。
static uint32_t g_next_timeout = 0; static uint32_t add_timer_to_active_list(void)
ルール4
volatileキーワードは、必要な場所で使用してください(ただし、可能な限りではありません-翻訳者のメモ)。
結果:割り込みサービスルーチンで変更できるグローバル変数の説明で使用します。 複数のタスクによって変更されたグローバル変数を記述する場合。 メモリにマップされた外部デバイスのレジスタを記述するとき。
証拠:このような変数を使用したコマンドの実行は多少遅くなります(これが注意の由来です)が、検出が困難なエラーを回避し、リーダーに追加情報を提供します。* 1。
uint16_t volatile g_state = SYSTEM_STARTUP; typedef struct { ... } my_fpga_t; my_fpga_t volatile *const p_timer = ...
ルール5
一時的にであっても、コードブロックを無効にするためにコメントを使用しないでください。
結果:プリプロセッサの条件付きコンパイルディレクティブを使用します。 方言Cで許可されている場合でも、ネストされたコメントを使用しないでください。 コードの実験にはバージョン管理システムを使用してください。 リリースにコメント付きのコードを残さないでください。
ルールは明確で複雑ではないようですが、手は/ *と* /を挿入するように手を伸ばします(他のルール-//のコメントはすでに受け入れられているため)-悪い習慣と戦わなければなりません。
#if 0 a = a + 1; #endif
ルール6
整数のビットまたはバイト単位の長さがプログラムにとって重要な場合、特定の長さのデータ型(C99型)を使用します。
結果:短いキーワードと長いキーワードは使用しないでください。 charキーワードは、文字列を定義するためにのみ使用されます。
証明:実行時に費用がかからず、移植性が向上します。* 1。
#include < stdint.h > uint16_t data;
ルール7
ビット演算(&、|、〜、<<、>>)を符号付きの型に適用しないでください。
結果:これらの操作のあいまいな動作を調査する必要はありません。
証明:実行時に費用がかからず、移植性が向上します。* 1。
ルール8
式または比較で、符号付き型と符号なし型を混在させないでください。
結果:符号なしデジタル定数にはuの接尾辞が必要です。
証拠:実行時に費用はかかりません。型キャストのルールを覚えておく必要はありません。* 1。
int s = - 9; unsigned int u = 6u; if (s + u < 4) { // , -9+6=-3 < 4, } else { // - }
ルール9
パラメーター化されたマクロを使用する代わりに、インライン関数を使用する必要があります。
結果:ルール2との組み合わせで、#defineの使用を完全にやや少なくします(コンパイルオプションは残ります)。
証明:原則としてマクロはデバッグされず、これは非常に適切な引数です。マクロを呼び出すときに型制御はありません。パラメーターを変更するときに副作用が発生する可能性がありますが、一部のCコンパイラーは、インラインディレクティブをアクションのガイドとしてではなく、希望として認識します-結果として、実行時間が長くなる可能性があります ただし、使用することを強くお勧めします。
#define SQUARE(A) ((A)*(A)) inline uint32_t square(uint16_t a) { return (a * a); }
ルール10
各変数を1行で宣言します。
結果:変数リストではカンマは無効です。
証明:どのくらい機能するかを覚える必要はありません;コードの可読性が向上します。
それにもかかわらず、私の意見では、コードの目に見えるサイズが大きくなるため、最も物議を醸すルールです。 宣言がモジュールの先頭でコンパイルされている場合(および使用前にローカル変数を宣言する推奨事項もあります)、私は個人的にコードの拡張を最良の側面に帰することはできないため、次のような宣言を使用し続けます:
int i,j,k;
しかし、もちろん、次の行は受け入れられません。
int *pi, pj;
まとめると、移動の不可能性をまったく妨げることなく、既存のロープをいくらか短くする方法があると言えます。 もちろん、推奨事項に従うことに狂信的になるべきではありません。ここでは、憲章のように、各行が独自の方法で行った人々の血で書かれていることに留意する必要があります。 さらに、どのルールも常識に反するものではなく、むしろ染み込んだ習慣に直面する場合があり、そのような合理性はまったく必要ありません。