次のenumおよびswitchステートメントを検討してください。
typedef enum {
MaskValueUno,
MaskValueDos
} testingMask;
void myFunction(testingMask theMask) {
switch (theMask) {
case MaskValueUno: {}// deal with it
case MaskValueDos: {}// deal with it
default: {} //deal with an unexpected or uninitialized value
}
};
私はObjective-Cプログラマーですが、より幅広い読者のためにこれを純粋なCで記述しました。
Clang/LLVM 4.1 with -Weverythingはデフォルト行で警告を出します:
すべての列挙値をカバーするスイッチのデフォルトラベル
さて、これがある理由が少しわかります。完全な世界では、引数theMask
に入力する値はenumだけなので、デフォルトは必要ありません。しかし、ハッキングが発生し、初期化されていないintが私の美しい関数にスローされた場合はどうなりますか?私の機能はライブラリのドロップとして提供され、そこに何が入るかを制御できません。 default
の使用は、これを処理する非常に優れた方法です。
なぜLLVMの神々は、この行動を彼らの地獄のデバイスに値しないと見なすのですか?引数を確認するには、ifステートメントを前に置く必要がありますか?
これは、clangのレポートの問題またはユーザーが保護しているバージョンのどちらにも影響を受けないバージョンです。
_void myFunction(testingMask theMask) {
assert(theMask == MaskValueUno || theMask == MaskValueDos);
switch (theMask) {
case MaskValueUno: {}// deal with it
case MaskValueDos: {}// deal with it
}
}
_
Killianは、clangが警告を発する理由を既に説明しています。列挙型を拡張すると、デフォルトのケースに陥ってしまい、おそらく望んでいないことになります。正しいことは、デフォルトのケースを削除して、nhandled条件の警告を取得することです。
ここで、誰かが列挙の外にある値で関数を呼び出すことができるかどうか心配しています。これは、関数の前提条件を満たしていないように聞こえます。testingMask
列挙からの値を期待するように文書化されていますが、プログラマーは別のものを渡しました。ですからプログラマエラーを使用してassert()
(またはObjective-Cを使用していると言ったようにNSCAssert()
)を使用します。プログラマが間違っている場合、プログラマが間違っていることを説明するメッセージでプログラムをクラッシュさせます。
ここにdefault
ラベルがあることは、期待していることについて混乱していることを示しています。可能なすべてのenum
値を明示的に使い果たしたので、default
は実行できない可能性があり、将来の変更から保護する必要もありません。enum
の場合、コンストラクトはalreadyで警告を生成します。
したがって、コンパイラーは、すべてのベースをカバーしたがthinkingのように見えることに気づきました。 switch
を期待される形式に変更するための最小限の労力を費やすことにより、コンパイラーに対して、実行しているように見えることが実際に実行していることであり、それを知っていることを示します。
しかし、ハッキングが発生し、初期化されていないintが私の美しい関数にスローされた場合はどうなりますか?
次に ndefined Behavior を取得し、default
は無意味になります。これを改善するためにできることは何もありません。
より明確にさせてください。初期化されていないint
が関数に渡された瞬間、それは未定義の動作です。あなたの関数は停止問題を解決でき、それは問題ではありません。 UBです。 UBが呼び出された後は、何も実行できません。
Clangは混乱しています。デフォルトのステートメントがあるため、完全に細かい慣行があります。これはdefensive programmingと呼ばれ、優れたプログラミング慣行と見なされています(1)。デスクトッププログラミングではないかもしれませんが、ミッションクリティカルなシステムで多く使用されています。
防御的プログラミングの目的は、理論的には決して発生しない予期しないエラーを検出することです。このような予期しないエラーは、必ずしもプログラマーが関数に誤った入力をしたり、「悪意のあるハッキング」であるとは限りません。可能性が高いのは、変数の破損が原因である可能性があります。バッファオーバーフロー、スタックオーバーフロー、暴走コード、および関数に関連しない同様のバグが原因の可能性があります。組み込みシステムの場合、特に外部RAM回路を使用している場合は、EMIが原因で変数が変わる可能性があります。
デフォルトのステートメント内に何を書き込むかについては、プログラムがそこに行き着いたら問題が起きていると思われる場合は、何らかのエラー処理が必要です。多くの場合、おそらく「予期しないが重要ではない」などのコメントを付けた空のステートメントを単に追加するだけで、ありそうもない状況を考えたことを示すことができます。
(1)MISRA-C:2004 15.3。
さらに良い:
typedef enum {
MaskValueUno,
MaskValueDos,
MaskValue_count
} testingMask;
void myFunction(testingMask theMask) {
assert(theMask >= 0 && theMask<MaskValue_count);
switch theMask {
case MaskValueUno: {}// deal with it
case MaskValueDos: {}// deal with it
}
};
これは、列挙型に項目を追加するときにエラーが発生しにくくなります。 enum値を符号なしにすると、> = 0のテストをスキップできます。この方法は、列挙値にギャップがない場合にのみ機能しますが、多くの場合そうです。
デフォルトのステートメントは必ずしも役立つとは限りません。スイッチが列挙型の上にある場合、列挙型で定義されていない値はすべて、未定義の動作を実行することになります。
ご存じのとおり、コンパイラーはそのスイッチを(デフォルトで)以下のようにコンパイルできます。
if (theMask == MaskValueUno)
// Execute something MaskValueUno code
else // theMask == MaskValueDos
// Execute MaskValueDos code
未定義の動作をトリガーすると、後戻りすることはできません。
また、すべてのケースでdefault:
を使用することも好みます。私はいつものようにパーティーに遅れますが、...上に見なかった他のいくつかの考え:
-Werror
もスローしている場合はエラー)は-Wcovered-switch-default
から(-Weverything
からではあるが-Wall
からではない)から発生しています。道徳的な柔軟性により特定の警告をオフオフにすることができる場合(つまり、-Wall
または-Weverything
からいくつかを削除する)、 -Wno-covered-switch-default
(または-Wno-error=covered-switch-default
を使用する場合は-Werror
)をスローし、一般に他の警告では-Wno-...
を使用すると不快になります。gcc
(およびclang
のより一般的な動作)については、gcc
のマンページで-Wswitch
、-Wswitch-enum
、-Wswitch-default
の(異なる)switchステートメントの列挙型の同様の状況での動作。私はこの警告の概念も好きではありませんし、その言葉遣いも好きではありません。私には、警告からの単語(「デフォルトラベル...はすべて...の値をカバーしています」)は、default:
ケースが常に実行されることを示唆しています。
switch (foo) {
case 1:
do_something();
//note the lack of break (etc.) here!
default:
do_default();
}
最初に読んだとき、これはあなたが遭遇していると私が思ったものです-default:
またはbreak;
がないため、return;
ケースは常に実行されます。この概念は、(私の耳にとって)clang
から生まれる他の乳母スタイル(たまに役立つが)のコメントと似ています。 foo == 1
の場合、両方の関数が実行されます。上記のコードにはこの動作があります。つまり、後続のケースからコードを実行し続ける可能性がある場合にのみ、ブレークアウトに失敗します!ただし、これは問題ではないようです。
徹底的であることのリスクがある、完全性のための他のいくつかの考え:
int
または何かをこの関数に渡そうとすると、明示的に独自の特定のタイプの場合、コンパイラーは積極的な警告またはエラーによってその状況で同様にあなたを保護する必要があります。しかし、そうではありません! (つまり、少なくともgcc
とclang
はenum
型チェックを行わないようですが、 icc
します )。 type-safetyを取得していないため、valueを取得できます上記の安全性。それ以外の場合は、TFAで提案されているように、struct
またはタイプセーフを提供できるものを検討してください。enum
などのMaskValueIllegal
に新しい「値」を作成し、case
でそのswitch
をサポートしないことです。それはdefault:
によって(他の奇抜な値に加えて)食べられます長寿命の防御的コーディング!
これが代替案です:
OPは、誰かがint
を渡した場合にenumが期待されるケースから保護しようとしています。または、より可能性が高いのは、誰かが古いライブラリを新しいケースに新しいヘッダーを使用して新しいプログラムにリンクした場合です。
int
ケースを処理するようにスイッチを変更しませんか?スイッチの値の前にキャストを追加すると、警告がなくなり、デフォルトが存在する理由についてある程度のヒントが得られます。
_void myFunction(testingMask theMask) {
int maskValue = int(theMask);
switch(maskValue) {
case MaskValueUno: {} // deal with it
case MaskValueDos: {}// deal with it
default: {} //deal with an unexpected or uninitialized value
}
}
_
これはmuchが各値をテストするassert()
よりも不快ではないこと、またはenum値の範囲が整然としていることを前提としていることがわかりましたより簡単なテストが機能するように。これは、デフォルトが正確かつ美しく行うことを行う醜い方法です。