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

EXP38-C. ビットフィールドメンバや無効な型で offsetof() を呼び出さない

offsetof() マクロは、構造体においてその要素名のオフセットを調べる移植性の高い仕組みを提供する。offsetof() マクロは標準ヘッダ stddef.h で次のように定義される。

offsetof(type, member-designator)

このマクロは size_t 型の整数定数式に展開し、その値は、(type が指示する)構造体の先頭から(member designator が指示する)構造体メンバまでのバイト数で表わされるオフセット値となる。

offsetof() マクロの member designator パラメータがビットフィールドを指定している場合、あるいは type パラメータに対する . 演算子の無効な右オペランドである場合、動作は未定義となる。C 標準の附属書 J 「未定義の動作」の 144 も参照すること。

C 標準 [ISO/IEC 9899:2011] セクション 7.19 には、次のように記載されている。

型およびメンバ指定子は、

static type t;

という宣言があった場合、式 &(t.member-designator) を評価した結果がアドレス定数になるものでなければならない(指定したメンバがビットフィールドの場合、その動作は未定義とする)。

違反コード (ビットフィールドメンバ)

ルールに違反したこのコード例では、ビットフィールド構造体メンバに対して offsetof() を呼び出しており、未定義の動作を引き起こす。

struct S {
  unsigned int a: 8;
} bits = {255};

int main(void) {
  size_t offset = offsetof(struct S, a);  /* エラー */
  printf("offsetof(bits, a) = %d.\n", offset );
  return 0;
}
処理系固有の詳細

Microsoft Visual Studio 2005 コンパイラは、offsetof() マクロにビットフィールドが member designator として渡されると、次のエラーを出す。

error C2104: '&' on bit field ignored
適合コード (ビットフィールドメンバ)

以下の適合コードでは、ビットフィールド以外の型(この場合 unsigned int)の構造体メンバに対して offsetof() を呼び出している。

struct S {
    unsigned int a;
} bits = {255};

int main(void) {
  size_t offset = offsetof(struct S, a);
  printf("offsetof(bits, a) = %d.\n", offset );
  return 0;
}
違反コード (無効な構造体)

古いコンパイラの中には、以下の例のように、使用されている型に対応しないメンバの指定を許可するものもある。

struct S {
  int i;
  int j;
} s;

struct T {
  float f;
} t;

int main(void) {
  return t.j;
}

これは、次のような最も短純な offsetof() マクロ実装と組み合わせられたときに、コンパイラが検出しない場合がある。

#define offsetof(type, field) ((size_t)&(((type *)0)->field))

この定義の offsetof() を使用し、誤ったメンバの使用を許可するコンパイラは、次に示す offsetof() の(誤った)使用を診断できない可能性がある。

int main(void) {
  return offsetof(struct T, j);
}

この問題は、古い組み込みコンパイラなどでは、まだ存在する可能性がある。

自動検出

member-designator が t のメンバでない場合、t.member-designator は無効となるため、その部分は言語定義によって捕捉され、解析ツールによって捕捉する必要はない。これは未定義の動作ではない。

ビットフィールドの場合、未定義の動作となる。ただし、通常のマクロ実装は次のようになる (フラットなアドレス空間が利用可能な場合)。

#define offsetof(type, member) ((size_t)&(((type *)0)->member))

メンバがビットフィールドの場合、& 演算子の制約違反となるため、このマクロ内のメンバのアドレスの使用は無効となる。

フラットでないアドレス空間の場合、通常はアドレス 0 の代わりに特別な名前付きオブジェクトが使用され、メンバのアドレスから構造体の先頭アドレスが減算されるため、マクロは前に示したのとほぼ同様となる。言語によっても、この例と同様にこれが問題にならないよう予防されている。

最近のコンパイラでは、offsetof() を次のように定義し、多くの作業をコンパイラで行っている。ここで、_Offsetof はコンパイラ組み込みの関数である。

#define offsetof(type, member) _Offsetof(type, member)

いずれにしても、ビットフィールドでの動作を実行しようとしている場合、コンパイラによって明確に把握される。

リスク評価

ルール

深刻度

可能性

修正コスト

優先度

レベル

EXP38-C

P2

L3

自動検出

ツール

バージョン

チェッカー

説明

EDG

 

 

 

GCC

V. 4.3.5

 

 

関連するガイドライン
ISO/IEC TR 24772:2013 Bit Representations [STR]
MISRA C:2012 Rule 1.3 (required)
参考資料
[ISO/IEC 9899:2011] Subclause 7.19, "Common Definitions <stddef.h>"
[Jones 2004]  
翻訳元

これは以下のページを翻訳したものです。

EXP38-C. Do not call offsetof() on bit-field members or invalid types (revision 51)

Top へ

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