Cで整数の個々のビットをテストおよび設定するという古典的な問題は、おそらく最も一般的な中級レベルのプログラミングスキルの1つです。次のような単純なビットマスクを使用して設定およびテストします
unsigned int mask = 1<<11;
if (value & mask) {....} // Test for the bit
value |= mask; // set the bit
value &= ~mask; // clear the bit
興味深いブログ投稿 は、これがエラーを起こしやすく、保守が難しく、実践が悪いと主張しています。 C言語自体は、タイプセーフで移植可能なビットレベルのアクセスを提供します。
typedef unsigned int boolean_t;
#define FALSE 0
#define TRUE !FALSE
typedef union {
struct {
boolean_t user:1;
boolean_t zero:1;
boolean_t force:1;
int :28; /* unused */
boolean_t compat:1; /* bit 31 */
};
int raw;
} flags_t;
int
create_object(flags_t flags)
{
boolean_t is_compat = flags.compat;
if (is_compat)
flags.force = FALSE;
if (flags.force) {
[...]
}
[...]
}
しかし、これは私を危機にします。
同僚と私がこれについて持っていた興味深い議論はまだ解決されていません。どちらのスタイルも機能し、古典的なビットマスク方式は簡単、安全、明確であると考えています。私の同僚は、それが一般的で簡単であることに同意しますが、ビットフィールド結合法は、移植性と安全性を高めるために余分な数行の価値があります。
どちらの側にも議論はありますか?特に、おそらくエンディアンで、ビットマスク法では見落とされるかもしれないが、構造法は安全である可能性のある障害がありますか?
「Cは機械語内のフィールドの順序付けを保証しません」( The C book )
それを無視して、正しく使用すると、どちらの方法も安全です。どちらの方法でも、積分変数へのシンボリックアクセスが可能です。ビットフィールドメソッドの方が簡単に書くことができますが、レビューするコードが増えます。
ビットの設定とクリアがエラーを起こしやすいという問題である場合、正しいことは、関数またはマクロを記述して正しく実行することです。
// off the top of my head
#define SET_BIT(val, bitIndex) val |= (1 << bitIndex)
#define CLEAR_BIT(val, bitIndex) val &= ~(1 << bitIndex)
#define TOGGLE_BIT(val, bitIndex) val ^= (1 << bitIndex)
#define BIT_IS_SET(val, bitIndex) (val & (1 << bitIndex))
ValがBIT_IS_SETを除いて左辺値でなければならないことを気にしない場合、コードが読みやすくなります。それで満足できない場合は、割り当てを取り出し、括弧で囲んでval = SET_BIT(val、someIndex);として使用します。これは同等です。
本当に答えは、やりたいことからやりたいことを切り離すことを検討することです。
ビットフィールドは素晴らしく読みやすいですが、残念ながらC言語はメモリ内のビットフィールドのレイアウトを指定していません、つまり、オンディスク形式またはバイナリワイヤプロトコルでパックされたデータを処理するために本質的に役に立たないことを意味します。あなたが私に尋ねると、この決定はCの設計エラーでした。リッチーは注文を選んでそれを使い続けることができたでしょう。
あなたは作家の観点からこれについて考えなければなりません-あなたの聴衆を知っています。そのため、考慮すべき「オーディエンス」がいくつかあります。
最初に、人生全体をビットマスクし、睡眠中にそれを行うことができる古典的なCプログラマーがいます。
次に、newbがいます。これは、これらすべてが何であるかわからない|&ものです。彼らは最後の仕事でphpをプログラミングしていましたが、今ではあなたのために働いています。 (私はこれをPHPをやっている初心者と言います)
最初の聴衆を満足させるために(1日中ビットマスクで)書くなら、あなたは彼らをとても幸せにし、彼らは目隠しされたコードを維持することができます。ただし、newbは、コードを維持する前に、大きな学習曲線を克服する必要があります。彼らは、バイナリ演算子、これらの操作を使用してビットを設定/消去する方法などについて学ぶ必要があります。
一方、第2の聴衆を満足させるために書く場合、newbsはコードを維持するのが簡単になります。彼らはより簡単に時間を費やします
flags.force = 0;
より
flags &= 0xFFFFFFFE;
最初の聴衆は不機嫌になりますが、新しい構文を理解して維持することができないと想像するのは困難です。それを台無しにするのははるかに難しいです。 newbはコードをより簡単に保守するため、新しいバグはありません。あなたは、「私の時代には、ビットをセットするために安定した手と磁化針が必要でした...ビットマスクさえ持っていませんでした!」 (ありがとう [〜#〜] xkcd [〜#〜] )。
したがって、コードをnewbセーフにするために、ビットマスク上のフィールドを使用することを強くお勧めします。
共用体の使用法は、ANSI C標準に従って未定義の動作を持っているため、使用しないでください(または少なくとも移植性があるとは見なされません)。
付録J-移植性の問題:
1以下は指定されていません:
—構造体またはユニオンに値を保存するときのパディングバイトの値(6.2.6.1)。
—(6.2.6.1)に保存されている最後のメンバー以外のユニオンメンバーの値。
6.2.6.1-言語の概念-型の表現-一般:
6値が構造体またはユニオン型のオブジェクト(メンバーオブジェクトを含む)に格納されている場合、パディングバイトに対応するオブジェクト表現のバイトは未指定の値を取ります。[42])構造体またはユニオンオブジェクトの値は構造体または共用体オブジェクトのメンバーの値がトラップ表現であっても、決してトラップ表現ではありません。
7値がユニオン型のオブジェクトのメンバーに格納されている場合、そのメンバーに対応しないが他のメンバーに対応するオブジェクト表現のバイトは、指定されていない値を取ります。
したがって、ビットフィールド↔整数の対応を維持し、移植性を維持したい場合は、リンクされたブログ投稿とは反対に、not悪い練習。
あなたがうんざりさせるビットフィールドアプローチについてはどうですか?
両方の手法にはそれぞれの位置があり、私が決定したのはどちらを使用するかだけです。
単純な「1回限りの」ビットをいじるには、ビットごとの演算子を直接使用します。
より複雑なもの、たとえばハードウェアレジスタマップの場合、ビットフィールドアプローチが優先されます。
ビット演算子では、典型的な(悪い)プラクティスはビットマスクの多数の#definesです。
ビットフィールドに関する唯一の注意点は、コンパイラがオブジェクトを必要なサイズに実際にパックしたことを確認することです。これが標準で定義されているかどうか覚えていないので、assert(sizeof(myStruct)== N)は便利なチェックです。
いずれにせよ、ビットフィールドは何十年もの間、GNUソフトウェアで使用されてきましたが、害はありません。関数のパラメーターとして気に入っています。
ビットフィールドは構造体ではなく従来であると主張します。誰もがさまざまなオプションをオフに設定する方法を知っており、コンパイラはこれを非常に効率的 CPUでのビット単位操作に要約します。
マスクとテストを正しい方法で使用すると、コンパイラが提供する抽象化により、堅牢でシンプル、読みやすく、クリーンになります。
オン/オフスイッチのセットが必要な場合、Cでそれらを使用し続けます。
両方のフィールドはアクセス可能であり、それらは交換可能に使用できるため、構造マッピングを間違えることはありません。
ビットフィールドの利点の1つは、オプションを簡単に集約できることです。
mask = USER|FORCE|ZERO|COMPAT;
vs
flags.user = true;
flags.force = true;
flags.zero = true;
flags.compat = true;
プロトコルオプションの処理などの一部の環境では、オプションを個別に設定したり、複数のパラメーターを使用して中間状態をフェリーしたりする必要があり、非常に古くなり、最終結果をもたらします。
しかし、時々flag.blahを設定し、IDEにリストをポップアップすることは、あなたが私のような人で、リストを常に参照せずに設定したいフラグの名前を思い出せない場合に特に便利です。
私は個人的にはブール型の宣言を避けます。ある時点で、トグルしたフィールドが他の「一見」のr/wステータスに依存していないという誤った印象に陥るからです。同じ32ビットWordを共有する無関係なフィールド。
私の投票は、状況の文脈に依存し、場合によっては両方のアプローチがうまくいくかもしれないということです。
C++では、std::bitset<N>
。
はい、エラーが発生しやすいです。私はこの種のコードで多くのエラーを見てきました。主に一部の人々は、完全に混乱した方法でそれとビジネスロジックを混乱させ、メンテナンスの悪夢を作成する必要があると感じているためです。彼らは、「本物の」プログラマーが_value |= mask;
_、_value &= ~mask;
_またはさらに悪いことをanyの場所で書くことができると考えています。さらに、インクリメント演算子、いくつかのmemcpy
、ポインタキャスト、およびその時点で不明瞭でエラーが発生しやすい構文があれば思い浮かぶでしょう。もちろん、一貫性を保つ必要はなく、ランダムに分散した2つまたは3つの異なる方法でビットを反転できます。
私のアドバイスは:
SetBit(...)
やClearBit(...)
などのメソッドでクラスにカプセル化します。 (モジュール内にCのクラスがない場合。)作業中に、すべての動作を文書化できます。あなたの最初の方法が望ましいです、私見。なぜ問題を難読化するのですか?ビットをいじるのは本当に基本的なことです。 Cはそれを正しかった。エンディアンは関係ありません。ユニオンソリューションが行う唯一のことは、名前を付けることです。 11は不可解かもしれませんが、意味のある名前に#defineするか、列挙型で十分です。
「|&^〜」のような基礎を処理できないプログラマは、おそらく間違った作業ラインにいます。
「c演算子」を検索したとき
最初の3ページは次のとおりです。
http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2Bhttp://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40F_HTML/AQTLTBTE/ DOCU_059.HTMhttp://www.cs.mun.ca/~michael/c/op.html
..だから、私はその言語に不慣れな人々についての議論は少しばかげていると思う。
まあ、それはそれを行う一つの方法だと思いますが、私は常に シンプルに保つ を好むでしょう。
慣れてしまえば、マスクの使用は簡単で、明確で、移植性があります。
ビットフィールドは簡単ですが、追加の作業を行わずに移植することはできません。
[〜#〜] misra [〜#〜] -compliantコードを記述する必要がある場合、MISRAガイドラインは、未定義または実装依存の動作。
私はほとんど常に、ビットマスクを使用した論理演算を直接またはマクロとして使用します。例えば.
#define ASSERT_GPS_RESET() { P1OUT &= ~GPS_RESET ; }
ちなみに、元の質問のあなたのユニオン定義は、私のプロセッサ/コンパイラの組み合わせでは機能しません。 int型の幅は16ビットのみで、ビットフィールドの定義は32です。少し移植性を高めるには、新しい32ビット型を定義する必要があります。移植運動。私の場合
typedef unsigned long int uint32_t
元の例では
typedef unsigned int uint32_t
typedef union {
struct {
boolean_t user:1;
boolean_t zero:1;
boolean_t force:1;
int :28; /* unused */
boolean_t compat:1; /* bit 31 */
};
uint32_t raw;
} flags_t;
オーバーレイされたintも符号なしにする必要があります。
一般的に、読みやすく理解しやすいのは、保守しやすいものです。 Cに慣れていない同僚がいる場合、おそらく「より安全な」アプローチが理解しやすいでしょう。
ビットフィールドは優れていますが、ビット操作操作はアトミックではないため、マルチスレッドアプリケーションで問題が発生する可能性があります。
たとえば、マクロは次のように仮定できます。
#define SET_BIT(val, bitIndex) val |= (1 << bitIndex)
| =は1つのステートメントであるため、アトミック操作を定義します。ただし、コンパイラーによって生成される通常のコードは、アトミックにしようとはしません。
したがって、複数のスレッドが異なるセットビット操作を実行する場合、セットビット操作の1つが誤っている可能性があります。両方のスレッドが実行されるため:
thread 1 thread 2
LOAD field LOAD field
OR mask1 OR mask2
STORE field STORE field
結果はfield '= field OR mask1 OR mask2(意図))、または結果はfield' = field OR mask1(意図しない)または結果はfield '= field OR mask2(意図しない)。