INT36-C. ポインタから整数への変換、整数からポインタへの変換
C では整数とポインタを同じもののように扱うことが多いが、ポインタから整数および整数からポインタへの変換は処理系定義であることに注意。
整数とポインタ間の変換は、処理系によっては望ましくない結果をもたらすことがある。C 言語規格セクション 6.3.2.3 には次のように記載されている[ISO/IEC 9899:2011]。
整数は任意のポインタ型に型変換できる。これまでに規定されている場合を除き、結果は処理系定義とし、正しく境界調整されていないかもしれず、被参照型の実体を指していないかもしれず、トラップ表現であるかもしれない。
任意のポインタ型は整数型に型変換できる。これまでに規定されている場合を除き、結果は処理系定義とする。結果が整数型で表現できなければ、その動作は未定義とする。結果は何らかの整数型の値の範囲に含まれているとは限らない。
附属書J「未定義の動作」の24も参照。
変換結果のポインタが正しく境界調整されない場合、被参照型の実体を指さない場合、またはトラップ表現となる場合には、整数型をポインタ型に変換してはならない。
変換結果の値を整数型で表すことができない場合はポインタ型を整数型に変換してはならない。
これらの問題が発生する原因は、ポインタから整数へ、あるいは整数からポインタへの変換が、実行環境のアドレッシング構造に則した動作をしなくてはならないことにある。たとえば、すべてのアーキテクチャがフラットなメモリモデルであるとは限らない。
違反コード
次の違反コード例では、ポインタを整数に変換している。たとえばポインタ型が64ビット、符号無し整数型が32ビットの場合、64ビット幅のデータであるポインタ型データの変換結果は32ビット幅の整数型には収まりきらない。
void f(void) { char *ptr; /* ... */ unsigned int number = (unsigned int)ptr; /* 違反 */ /* ... */ }
適合コード
void
へのポインタは intptr_t
や uintptr_t
に変換することができ、さらに void
へのポインタに変換して戻した場合、値は変わらない(INT36-EX2 を参照)。実際、C 標準では、void
へのポインタと何らかのオブジェクト型へのポインタとの間の変換が可能であり、一方から他方への変換を行った後さらに元の型に変換した結果は、元のポインタの値と等しくなることが保証されている。したがって、uintptr_t
型をサポートする処理系では、この適合コードのように (char *)
ポインタから uintptr_t
への変換を行うことが可能である。
void f(void) { char *ptr; /* ... */ uintptr_t number = (uintptr_t)ptr; /* ... */ }
違反コード
次のコード例では、ポインタ ptr
を整数値 number
に変換している。number
の上位9ビットは flag
の値を保持するために使用され、結果はポインタに変換し直される。しかし、たとえばポインタが64ビット幅で符号無し整数が32ビット幅の処理系では64ビット幅のポインタの変換結果は32ビット幅の整数型には収まりきらない。
char *ptr; unsigned int flag; /* ... */ unsigned int number = (unsigned int)ptr; number = (number & 0x7fffff) | (flag << 23); ptr = (char *)number;
同じような手法が Emacs の初期のバージョンで使用されており、可搬性が悪くなるとともに、8MBを超えるサイズのファイル編集を不可能にしていた。
このコード例は「EXP11-C. ビットフィールド構造体のレイアウトについて勝手な想定をしない」にも違反している。
適合コード
このコードでは、struct
を使用してポインタとフラグ値を格納している。このコードは、ワードサイズが32ビットより小さい場合大きい場合どちらにも対応できる。また、ポインタがいかなる整数型にも変換できないような場合であっても動作する。
struct ptrflag { char *pointer; unsigned int flag :9; } ptrflag; char *ptr; unsigned int flag; /* ... */ ptrflag.pointer = ptr; ptrflag.flag = flag;
違反コード
低レベルのカーネルやグラフィックス処理のコードでは、特定のメモリ番地にアクセスするため、整数リテラルからポインタへの変換を必要とすることがある。次のコードでは、ポインタ型変数に整数定数を直接代入しているが、結果が意図した通りになるかは分からない。
unsigned int *g(void) { unsigned int *ptr = 0xdeadbeef; /* ... */ return ptr; }
代入の結果は処理系定義である。適切に境界調整されないかもしれないし、被参照型のエントリを指していないかもしれないし、あるいはトラップ表現になるかもしれない。
適合コード
明示的にキャストすることで、コンパイラが整数値を適切なポインタ値に変換する役に立つかもしれない。
よく使われる手法は、変換したい整数をまず volatile 修飾した intptr_t
あるいは uintptr_t
型のオブジェクトに代入し、それをポインタ型に変換する、というものである。
unsigned int *g(void) { volatile uintptr_t iptr = 0xdeadbeef; unsigned int *ptr = (unsigned int *)iptr; /* ... */ return ptr; }
通常、volatile 修飾子がある場合にはコンパイラはポインタへの整数代入をチェックしなくなる。
例外
INT36-EX1: null ポインタは整数に変換でき、値 0 となる。同様に、整数 0 はポインタに変換でき、null ポインタとなる。
INT36-EX2: 有効な void
へのポインタは intptr_t
や uintptr_t
に変換でき、さらに元の型に変換して戻したときに値は変更されない。この条件は、intptr_t
や uintptr_t
が typedef
を用いて宣言されている場合は宣言に使われた型にも適用される。また、intptr_t
や uintptr_t
の同義語として新たな型が typedef
されている場合はその新たな型にも適用される。
void h(void) { intptr_t i = (intptr_t)(void *)&i; uintptr_t j = (uintptr_t)(void *)&j; void *ip = (void *)i; void *jp = (void *)j; assert(ip == &i); assert(jp == &j); }
リスク評価
ポインタから整数への変換、あるいはその逆の変換を行うと、移植性のないコードになり、無効なメモリ番地を参照する想定外のポインタを生成してしまうかもしれない。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
INT36-C |
低 |
中 |
高 |
P2 |
L3 |
自動検出
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE | |||
Coverity | 6.5 | POINTER_CONVERSION_LOSES_BITS | 実装済み |
LDRA tool suite |
V. 8.5.4 |
94 S |
実装済み |
PRQA QA-C | 8.1 | 0309 (U) | 部分的に実装済み |
関連するガイドライン
CERT C++ Secure Coding Standard | INT11-CPP. Take care when converting from pointer to integer or integer to pointer |
ISO/IEC TR 24772:2013 | Pointer Casting and Pointer Type Changes [HFC] |
ISO/IEC TS 17961 (ドラフト) | Converting a pointer to integer or integer to pointer [intptrconv] |
MITRE CWE | CWE-466, Return of pointer value outside of expected range CWE-587, Assignment of a fixed address to a pointer |
参考資料
[ISO/IEC 9899:2011] | Section 6.3.2.3, "Pointers" |
翻訳元
これは以下のページを翻訳したものです。
INT36-C. Converting a pointer to integer or integer to pointer (revision 94)