EXP11-C. ビットフィールド構造体のレイアウトについて勝手な想定をしない
ビットフィールド構造体の内部表現には、処理系依存の様々な性質がある(たとえば内部パディング)。また、ビットフィールド構造体には以下に示す処理系依存の制約がある。
- 記憶域単位におけるビットフィールドのアラインメント。たとえば、ビットフィールドは記憶域単位の上端から割り当ててもよいし、下端から割り当ててもよい。
- ビットフィールドが記憶域単位の境界をまたいでもよいか否か。
従って、ビットフィールド構造体のメンバのレイアウトに関してある状態を仮定した、移植性があり安全なコードを書くことは不可能である。
違反コード (ビットフィールドのアラインメント)
ビットフィールドを使用することで、フラグや範囲の狭い他の整数値をひとまとめにしてメモリ領域を節約することができる。またビットフィールドを使用することで構造体の記憶域の効率が向上する。コンパイラは一般に、記憶域単位にぴったりはまる限り、隣合うビットフィールド構造体のメンバを同じ int
幅の記憶域に割り当てる。しかし、記憶域単位にどの順序で割り当てるかは処理系依存である。処理系によっては、右から左に、つまり最初のメンバが記憶域単位の下位に割り当てられたり、左から右に、つまり最初のメンバが記憶域単位の上位に割り当てられたりするのである。ビットの順序に依存するような演算は、処理系が異なれば違う結果を生成するかもしれない。
4つの8ビットのビットフィールドメンバから成る以下の構造体について考えてみよう
struct bf { unsigned int m1 : 8; unsigned int m2 : 8; unsigned int m3 : 8; unsigned int m4 : 8; }; /* 合計 32 ビット */
右から左に割り当てる処理系では、struct bf
を以下のフォーマットで一つの記憶域単位に割り当てる
m4 m3 m2 m1
逆に、左から右に割り当てる処理系では、struct bf
を以下のフォーマットで一つの記憶域単位に割り当てる
m1 m2 m3 m4
以下のコードは、処理系が「左から右」系か「右から左」系かによって異なる動作をする。
struct bf { unsigned int m1 : 8; unsigned int m2 : 8; unsigned int m3 : 8; unsigned int m4 : 8; }; /* 合計 32 ビット */ void function() { struct bf data; unsigned char *ptr; data.m1 = 0; data.m2 = 0; data.m3 = 0; data.m4 = 0; ptr = (unsigned char *)&data; (*ptr)++; /* data.m1 か data.m4 のどちらかをインクリメントする */ }
適合コード (ビットフィールドのアラインメント)
この適合コードでは、どのフィールドを変更するかが明確である。
struct bf { unsigned int m1 : 8; unsigned int m2 : 8; unsigned int m3 : 8; unsigned int m4 : 8; }; /* 合計 32 ビット */ void function() { struct bf data; data.m1 = 0; data.m2 = 0; data.m3 = 0; data.m4 = 0; data.m1++; }
違反コード (ビットフィールドのオーバーラップ)
以下のコード例は、8ビットが1バイトであると仮定している。6ビットと4ビットのビットフィールドを宣言すると、各ビットフィールドは1バイトに含まれるのか、あるいは複数のバイトにまたがって分割されるのか、どちらだろうか?
struct bf { unsigned int m1 : 6; unsigned int m2 : 4; }; void function() { unsigned char *ptr; struct bf data; data.m1 = 0; data.m2 = 0; ptr = (unsigned char *)&data; ptr++; *ptr += 1; /* これは何をインクリメントするだろうか? */ }
仮に各ビットフィールドが各々のバイトに確保されるとすると、m2
(あるいは m1
、アラインメントによる) が 1 インクリメントされる。もしこれらのビットフィールドが 8 ビットのバイトにすきまなく詰め込まれると、m2
が 4 インクリメントされるかもしれない。
適合コード (ビットフィールドのオーバーラップ)
この適合コードでは、どのフィールドを変更するかが明確である。
struct bf { unsigned int m1 : 6; unsigned int m2 : 4; }; void function() { struct bf data; data.m1 = 0; data.m2 = 0; data.m2 += 1; }
リスク評価
タイプキャストされたデータの型とくにビットフィールドについて間違った思い込みをすると、予期せぬデータの値が得られる恐れがある。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
EXP11-C |
中 |
中 |
中 |
P8 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|
|
このレコメンデーションの違反を検出できる。特に以下の場合についてレポートする
|
LDRA tool suite |
V. 8.5.4 |
94 S |
実装済み |
PRQA QA-C | 8.1 | 0310 | 部分的に実装済み |
関連するガイドライン
CERT C++ Secure Coding Standard | EXP11-CPP. Do not apply operators expecting one type to data of an incompatible type |
ISO/IEC TR 24772:2013 | Bit Representations [STR] |
MISRA-C | Rule 3.5 |
参考資料
[Plum 1985] | Rule 6-5: In portable code, do not depend upon the allocation order of bit-fields within a word |
翻訳元
これは以下のページを翻訳したものです。
EXP11-C. Do not make assumptions regarding the layout of structures with bit-fields (revision 97)