1

ビットフラグの楽しさを発見しました。C でのビットフラグの使用に関する「ベスト プラクティス」に関連する質問がいくつかあります。Web で見つけたさまざまな例からすべてを学びましたが、まだ疑問があります。

スペースを節約するために、構造体 ( ) で単一の 32 ビット整数フィールドを使用して、A->flagブール型プロパティのいくつかの異なるセットを表しています。全部で 20 の異なるビットが#defined です。これらのいくつかは、真の存在/不在フラグです (STORAGE-INTERNAL と STORAGE-EXTERNAL)。2 つ以上の値を持つものもあります (たとえば、相互に排他的な形式のセット: FORMAT-A、FORMAT-B、FORMAT-C)。特定のビットを設定する (同時に相互に排他的なビットをオフにする) マクロを定義しました。特定のビットの組み合わせがフラグに設定されているかどうかをテストするためのマクロも定義しました。

ただし、上記のアプローチで失われるのは、列挙型によって最もよくキャプチャされるフラグの特定のグループ化です。関数を記述する場合、関数定義が見栄えがするように列挙型 (STORAGE-TYPE や FORMAT-TYPE など) を使用したいと考えています。パラメータを渡すためにのみ列挙型を使用し、フラグの設定とテストには #defined マクロを使用することを期待しています。

  1. A->flag(a) (32 ビット/64 ビット プラットフォーム間で) 移植可能な方法でフラグ ( ) を 32 ビット整数として定義するにはどうすればよいですか?

  2. A->flag(b) 保存方法と#defined 定数と列挙型の潜在的なサイズの違いについて心配する必要がありますか?

  3. (c) 私は物事を不必要に複雑にしていますか? つまり、パラメーターを通常の s#defineとして渡すために d 定数を使用することに固執する必要がありますか? intこのすべてで、他に何を心配する必要がありますか?

わかりにくい質問で申し訳ありません。潜在的な問題についての私の無知を反映しています。

4

6 に答える 6

7

その正確な問題 (a) を解決することを目的とした C99 ヘッダーがありますが、何らかの理由で Microsoft はそれを実装していません。幸いなことに、ここで Microsoft Windowsを入手でき<stdint.h>ます。他のすべてのプラットフォームにはすでにそれがあります。32 ビットの int 型はuint32_tint32_tです。これらには、8、16、および 64 ビットのフレーバーもあります。

それで、それは(a)を処理します。

(b) と (c) は一種の同じ質問です。何かを開発するときはいつでも仮定を行います。C が使用可能になると仮定します。<stdint.h>どこかで見つかると思います。int常に少なくとも 16 ビットであると想定できますが、現在では >= 32 ビットの想定がかなり妥当です。

一般に、レイアウトに依存しない適合プログラムを作成するように努める必要がありますが、それらは語長について仮定を行います。アルゴリズム レベルでのパフォーマンスについて心配する必要があります。

(a) パフォーマンスの遅れに気づき、(b) プログラムのプロファイルを作成するまでは、操作レベルでのパフォーマンスについて心配する必要はありません。個々の操作について心配することなく、行き詰まることなく仕事を終わらせる必要があります。:-)

あ、 C言語でプログラムを書く場合は、特に低レベルの操作性を気にする必要はありません。C は、金属に近い言語であり、可能な限り高速に動作します。私たちは日常的にphp、python、ruby、またはlispで何かを書いています。強力な言語が必要であり、最近ではCPUが非常に高速であるため、インタープリター全体で逃げることができます。ビットツイドルワードの完璧ではない選択は気にしないでください。 -長さの操作。:-)

于 2009-10-03T23:30:48.913 に答える
6

ビットフィールドを使用して、コンパイラにビット調整を行わせることができます。例えば:

struct PropertySet {
  unsigned internal_storage : 1;
  unsigned format : 4;
};

int main(void) {
  struct PropertySet x;
  struct PropertySet y[10]; /* array of structures containing bit-fields */
  if (x.internal_storage) x.format |= 2;
  if (y[2].internal_storage) y[2].format |= 2;
  return 0;
}

構造体の配列を追加するために編集

于 2009-10-03T23:40:48.113 に答える
3

他の人が言っているように、あなたの問題(a)は、または<stdint.h>のいずれかuint32_tを使用することで解決uint_least32_tできます(36ビットワードのBurroughsメインフレームについて心配したい場合)。MSVCはC99をサポートしていませんが、@ DigitalRossは、MSVCで使用するのに適したヘッダーを取得できる場所を示しています。

あなたの問題(b)は問題ではありません。Cは、必要に応じて安全に変換を入力しますが、おそらく必要ではありません。

最も懸念される領域は(c)であり、特にフォーマットサブフィールドです。そこでは、3つの値が有効です。これを処理するには、3ビットを割り当て、3ビットフィールドが値1、2、または4のいずれかである必要があります(設定されているビットが多すぎるか少なすぎるため、他の値は無効です)。または、2ビットの数値を割り当てて、0または3(または、本当に必要な場合は1または2のいずれか)が無効であることを指定することもできます。最初のアプローチはもう1ビットを使用しますが(32ビットのうち20ビットしか使用していないため、現在は問題ありません)、純粋なビットフラグアプローチです。

関数呼び出しを作成する場合、特に問題はありません。

some_function(FORMAT_A | STORAGE_INTERNAL, ...);

これは、FORMAT_Aが#defineまたはenumのどちらであっても機能します(enum値を正しく指定している場合)。呼び出されたコードは、発信者が集中力を失ったかどうかを確認し、次のように記述します。

some_function(FORMAT_A | FORMAT_B, ...);

ただし、これはモジュールの内部チェックであり、モジュールのユーザーが心配するチェックではありません。

メンバーのビットを頻繁に切り替える場合flagsは、フォーマットフィールドを設定および設定解除するためのマクロが役立つ場合があります。ただし、純粋なブールフィールドではほとんど必要ないと主張する人もいるかもしれません(そして私は同情します)。flagsメンバーを不透明として扱い、すべてのフィールドを取得または設定するための「関数」(またはマクロ)を提供するのが最適な場合があります。間違える人が少なければ少ないほど、間違えることは少なくなります。

ビットフィールドの使用が効果的かどうかを検討してください。私の経験では、それらは大きなコードにつながり、必ずしも非常に効率的なコードではありません。YMMV。

うーん...これまでのところ、ここで決定的なものは何もありません。

  • #define値が表示されないデバッガーで表示されることが保証されているため、すべてに列挙型を使用します。
  • 私はおそらくビットを取得または設定するためのマクロを提供しないでしょうが、私は時々残酷な人です。
  • フラグフィールドのフォーマット部分を設定する方法についてのガイダンスを提供し、それを行うためのマクロを提供する場合があります。

このように、おそらく:

enum { ..., FORMAT_A = 0x0010, FORMAT_B = 0x0020, FORMAT_C = 0x0040, ... };
enum { FORMAT_MASK = FORMAT_A | FORMAT_B | FORMAT_C };

#define SET_FORMAT(flag, newval)    (((flag) & ~FORMAT_MASK) | (newval))
#define GET_FORMAT(flag)            ((flag) & FORMAT_MASK)

SET_FORMAT正確に使用すれば安全ですが、乱用すると恐ろしいです。マクロの利点の1つは、必要に応じてマクロを徹底的に検証する関数に置き換えることができることです。これは、人々がマクロを一貫して使用する場合にうまく機能します。

于 2009-10-04T00:48:56.900 に答える
2

質問 a については、C99 を使用している (おそらく使用している) 場合は、uint32_t事前定義された型を使用できます (または、事前定義されていない場合は、stdint.hヘッダー ファイルで見つけることができます)。

于 2009-10-03T23:17:22.280 に答える
1

(c)について:列挙が正しく定義されていれば、問題なく引数として渡すことができるはずです。考慮すべきいくつかの事柄:

  1. 列挙型ストレージはコンパイラ固有であることが多いため、実行している開発の種類によっては(Windows、Linux、組み込みLinux、組み込みLinuxのどちらであるかは言及しません:))、列挙型ストレージのコンパイラオプションにアクセスすることをお勧めします。そこに問題がないことを確認してください。私は一般的に、コンパイラが列挙を適切にキャストする必要があるという上記のコンセンサスに同意しますが、注意する必要があります。
  2. 組み込み作業を行っている場合、列挙型、#defines、およびビットフィールドを使いこなすと、PCLintなどの多くの静的品質チェックプログラムが「吠え」ます。高品質のゲートを通過する必要がある開発を行っている場合、これは覚えておくべきことかもしれません。実際、一部の自動車規格(MISRA-Cなど)は、ビットフィールドでトリガーを取得しようとすると、まったくイライラします。
  3. 「ビットフラッグの楽しさを発見したばかりです。」私はあなたに同意します-私はそれらが非常に役立つと思います。
于 2009-10-04T01:09:06.313 に答える
0

上記の各回答にコメントを追加しました。明晰さがあると思います。デバッガーに表示され、フィールドが分​​離されているため、列挙型はよりクリーンなようです。マクロを使用して値を設定および取得できます。

また、列挙型が小さな整数として格納されていることも読みました。これは、ブール値テストでは右端のビットから実行されるため、これは問題ではありません。しかし、列挙型を使用して大きな整数 (1 << 21) を格納できますか??

皆さんに改めて感謝します。私はすでに 2 日前よりも多くのことを学びました!!

〜ラス

于 2009-10-04T14:04:11.887 に答える