INT00-C. 処理系のデータモデルについて理解する
データモデル は、標準データ型に割り当てられるサイズを定義する。処理系が使用するデータモデルについて理解することは重要である。しかしながら、言語標準が保証しない仮定に依存するようなコードを書く場合、静的アサートを使用し、その仮定が有効であることを保証すること。(「DCL03-C. 定数式の値をテストするには静的アサートを使う」を参照。) たとえば整数のサイズに関する仮定は、32ビットアーキテクチャから64ビットアーキテクチャにコードを移植する際に無効になるかもしれない。
一般的なデータモデル
データ型 |
iAPX86 |
IA-32 |
IA-64 |
SPARC-64 |
ARM-32 |
Alpha |
64-bit Linux, FreeBSD, |
---|---|---|---|---|---|---|---|
|
8 |
8 |
8 |
8 |
8 |
8 |
8 |
|
16 |
16 |
16 |
16 |
16 |
16 |
16 |
|
16 |
32 |
32 |
32 |
32 |
32 |
32 |
|
32 |
32 |
32 |
64 |
32 |
64 |
64 |
|
N/A |
64 |
64 |
64 |
64 |
64 |
64 |
ポインタ |
16/32 |
32 |
64 |
64 |
32 |
64 |
64 |
コードの中で、あるデータモデルを仮定することはよくある。たとえばあるコードベースでは、ポインタと long
型が同じサイズであることを要求し、他の大規模なコードベースでは int
型と long
型が同じサイズであることを要求するという具合である[van de voort 2007]。このような仮定をすることはよくある話ではあるが、コードは移植しにくくなり、また移植するとエラーが起きやすくもなる。解決法のひとつは、処理系定義となっている動作をさけることである。しかし、こうすると効率の悪いコードになる可能性がある。別の解決法としては、プラットフォーム依存な仮定をしている部分に静的アサートもしくは実行時アサートを導入し、移植する際に発見と修正が容易になるようにすることである。
<limits.h>
ある型に割り当てられたビット数を知っておくことは重要だ。しかし、さらに重要なことは、limits.h
で定義されているマクロを使えば、標準データ型の整数のとりうる範囲を調べられる、ということである。たとえば、UINT_MAX
は unsigned int
のとりうる最大値を示し、LONG_MIN
は long int
のとりうる最小値を示す。
<stdint.h>
stdint.h
ヘッダファイルでは、特定のサイズ制限がされた型が定義されており、特定のデータモデルへの依存を避ける目的でこれを使用できる。たとえば、int_least32_t
型は、処理系がサポートする最小の符号付き整数型を、最低でも32ビットの幅を持つように定義している。uint_fast16_t
型は、処理系がサポートする最速の符号なし整数型を、最低でも16ビットの幅を持つように定義している。intmax_t
型はその処理系で最も大きな符号付き整数型を定義し、uintmax_t
型はその処理系で最も大きな符号なしの型を定義している。以下に示す型はあらゆる処理系で使用できることが要求されている。
最小幅指定整数型 |
signed |
unsigned |
---|---|---|
8ビット |
|
|
16ビット |
|
|
32ビット |
|
|
64ビット |
|
|
最速最小幅整数型 |
signed |
unsigned |
8ビット |
|
|
16ビット |
|
|
32ビット |
|
|
64ビット |
|
|
最大幅整数型 |
signed |
unsigned |
maximum |
|
|
処理系によっては、ぴったり8ビットの型 int8_t
や 変換された void *
を保持するのに十分なサイズを持つ uintptr_t
などの型を追加でサポートする場合もある。
<inttypes.h>
inttypes.h
ヘッダは、最大幅の整数を操作する関数、および数値文字列を最大幅の整数に変換する関数を宣言している。
違反コード
以下のコード例は、long
型の値を int
型に読み込んでいる。これがうまくいくのは、sizeof(int) == sizeof(long)
が成り立つデータモデルの場合である。他のデータモデルでは、バッファオーバーフローにも似た予期せぬメモリ書き込みを引き起こす。
FILE *fp; int x; /* ... */ if (fscanf(fp, "%ld", &x) < 1) { /* エラー処理 */ }
コンパイラによっては、定数書式文字列が引数の型にマッチしない場合に警告を発することもある。
適合コード
以下の適合コードでは型に対して正しい書式を用いている。
FILE *fp; int x; /* fp を初期化 */ if (fscanf(fp, "%d", &x) < 1) { /* エラー処理 */ }
違反コード
以下のコード例は2つの unsigned int
値の乗算結果のすべてのビットが unsigned long
型として保持できることを保証しようとしている。この方法は、たとえば64ビット版Linuxなどのプラットフォームではうまくいくだろうが、64ビット版Microsoft Windwos などの環境ではうまくいかない。
unsigned int a, b; unsigned long c; /* a と b を初期化 */ c = (unsigned long)a * b; /* フィットするとは保証されない */
適合コード
以下の適合コードでは、結果の値を保持できることが保証されている、利用可能な最大の符号なし整数型を使用している。もしその型を利用できないのであれば、別の方法を探さなければならない。その場合は「INT32-C. 符号付き整数演算がオーバーフローを引き起こさないことを保証する」を参照のこと。
#if UINT_MAX > UINTMAX_MAX/UINT_MAX #error No safe type is available. #endif /* ... */ unsigned int a, b; uintmax_t c; /* a と b を初期化 */ c = (uintmax_t)a * b; /* フィットすることが保証されている */
リスク評価
処理系が使用しているデータモデルを理解することは、整数型のサイズやそのデータモデルで表現できる値の範囲に関するエラーを回避するために必須である。データ型のサイズに関する勝手な思い込みはバッファオーバーフローのような攻撃につながるおそれがある。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
INT00-C |
高 |
低 |
高 |
P3 |
L3 |
自動検出
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
PRQA QA-C | 8.1 | 0206 (U) | 部分的に実装済み |
関連するガイドライン
CERT C++ Secure Coding Standard | INT00-CPP. Understand the data model used by your implementation(s) |
ISO/IEC TR 24772:2013 | Bit Representations [STR] |
参考資料
[Open Group 1997a] |
[van de Voort 2007] |
翻訳元
これは以下のページを翻訳したものです。
INT00-C. Understand the data model used by your implementation(s) (revision 68)