EXP39-C. 適合しない型のポインタを使って変数にアクセスしない
適合しない型のポインタ (unsigned char
以外) を通じて変数を変更すると予期せぬ結果を引き起こす可能性がある。多くの場合、これは別名付け (aliasing) の規則に違反することにより引き起こされる。C 標準 [ISO/IEC 9899:2011] セクション 6.5 のパラグラフ 7 には、オブジェクトの別名付けが行われる、または行われない状況について記載されている。
オブジェクトに格納された値に対するアクセスは,次のうちのいずれか1つの型を持つ左辺値によらなければならない。
- オブジェクトの有効型と適合する型
- オブジェクトの有効型と適合する型の修飾版
- オブジェクトの有効型に対応する符号付き型または符号無し型
- オブジェクトの有効型の修飾版に対応する符号付き型または符号無し型
- メンバの中に上に列挙した型の1つを含む集成体型または共用体型(再帰的に包含されている部分集成体または含まれる共用体のメンバを含む)
- 文字型
他の左辺値の式 (unsigned char
以外) によりオブジェクトにアクセスすると、未定義の動作となる。C 標準の附属書 J 「未定義の動作」の 34 を参照すること。
違反コード
以下のコード例では、float 型のオブジェクトは int
, ip
へのポインタを通してインクリメントされるため、診断が必要である。
void f(void) { if (sizeof(int) == sizeof(float)) { float f = 0.0f; int *ip = (int *)&f; printf("float is %f\n", f); (*ip)++; // 診断が必要 printf("float is %f\n", f); } }
適合コード
以下のコード例では、int
, ip
へのポインタは float
, fp
へのポインタに置き換えられている。
void f(void) { if (sizeof(int) == sizeof(float)) { float f = 0.0f; float *fp = &f; printf("float is %f\n", f); (*fp)++; printf("float is %f\n", f); } }
違反コード
このコード例のプログラマは、最後に書き出された共用体メンバとは別のメンバから読み取ろうとしている。これを型のパニング(type-punning)という。
union a_union { int i; double d; }; int f() { a_union t; int *ip; t.d = 3.0; ip = &t.i; return *ip; }
ただし、共用体メンバから直接読み取る代わりに、整数値を参照するためのポインタ ip
を割り当て、ポインタによって参照される値を返している。ところが、これは厳密な別名付け (aliasing) の規則に違反し、この場合はコンパイラによって ip
が t.i
によって格納された値とは別の値を参照していると判断され、期待される値とは別の値が返される可能性がある。
違反コード
以下のコード例では、アドレスを使用し、得られたポインタをキャストし、結果を参照することによってアクセスしているため、キャストに union 型を使用してはいるが、未定義の動作を引き起こす。
union a_union { int i; double d; }; int f() { double d = 3.0; return ((union a_union *) &d)->i; }
最適化により、関数 f()
は期待した値以外の値を返すことがある。
適合コード
union 型を使用してメモリへにアクセスする場合、型のパニングが使用できる。以下のコード例は、期待される値を返す。
union a_union { int i; double d; }; int f() { a_union t; t.d = 3.0; return t.i; }
違反コード
以下のコード例では、2つの short 型オブジェクトの配列が int 型オブジェクトとして扱われており、整数値が代入されている。2つの short 型の値は未定義である。
short a[2]; a[0]=0x1111; a[1]=0x1111; *(int *)a = 0x22222222; /* 別名づけ(aliasing)ルールに違反している */ printf("%x %x\n", a[0], a[1]);
このコードを変換する際に、処理系では整数ポインタによるアクセスを介して short
型の値からなる配列 a
を変更することはできないと想定しているかもしれない。したがって、printf
は a[0]
と a[1]
の元の値で呼び出されるかもしれない。実際の動作は処理系定義であり、最適化レベルで変更できる。
処理系固有の詳細
最近の GCC は -O2
を指定するとデフォルトで -fstrict-aliasing
オプション(エイリアスベースの最適化)を有効にする。一部のアーキテクチャでは、結果として "1111 1111" と出力される。最適化しなければ期待どおりの値 "2222 2222" を出力する。
古くて品質の悪いコードに対して、エイリアスベースの最適化を無効にするには、 オプション -fno-strict-aliasing
を使うことができる。オプション -fstrict-aliasing
が有効になっている場合、オプション -Wstrict-aliasing
(これは -Wall
に含まれている) は別名づけ(aliasing)ルールに対する違反のいくつか(すべてではない)を警告する。
GCC 3.4.6 で最適化を使用してこのコードをコンパイルすると、別名を付けたポインタによる代入は効果的に削除される。
適合コード
以下のコード例では、オブジェクトの有効な型と互換性のある型を持つ union
型を使用している。
union { short a[2]; int i; } u; u.a[0]=0x1111; u.a[1]=0x1111; u.i = 0x22222222; printf("%x %x\n", u.a[0], u.a[1]);
このコード例では確実に "2222 2222" と出力される。
リスク評価
パフォーマンス向上のために最適化を行った結果、このように発見することが非常に困難な aliasing エラーが発生する場合がある。それだけでなく、上記の例のように、予期せぬ結果がバッファオーバーフロー攻撃やセキュリティチェックの回避、予想外の実行につながる恐れもある。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
EXP39-C |
中 |
低 |
高 |
P2 |
L3 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
PRQA QA-C | 8.1 |
0310 |
部分的に実装済み |
関連するガイドライン
ISO/IEC TS 17961 (ドラフト) | Accessing an object through a pointer to an incompatible type [ptrcomp] |
参考資料
[Acton 2006] | "Understanding Strict Aliasing" |
GCC Known Bugs | "C Bugs, Aliasing Issues while Casting to Incompatible Types" |
GCC Manual | |
[ISO/IEC 9899:2011] | Section 6.5, "Expressions" |
[Walfridsson 2003] | Aliasing, Pointer Casts and GCC 3.3 |
翻訳元
これは以下のページを翻訳したものです。
EXP39-C. Do not access a variable through a pointer of an incompatible type (revision 47)