JPCERT コーディネーションセンター

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
95 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)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter