[C] ポインタのアドレス参照範囲と未定義処理

2017年4月10日月曜日

C

不正ポインタ

CでNULLポインタや解放済みのメモリ領域を指すポインタ(ダングリングポインタ)を参照すると、未定義の動作となる。
バッファオーバーランとかの書き込みではなく、厳密にいうと参照だけで未定義動作になるんだね。

ただし、ポインタが指している領域ではなく、ポインタが保持している値(アドレス)そのものについてはルールが違う。

NULLポインタの場合はポインタの値を参照していい。そうじゃないと、そもそもNULLかどうかのチェックができないよね。
    /* これはできるよね! */
    if (p == NULL)

それ以外でオブジェクトが配置されている領域やmallocで確保した領域以外を指しているポインタの値は本来参照できないことになっている。
こういったポインタは不正ポインタと呼ばれるけど、代表的なのは未初期化のローカル変数やfreeで解放した後のポインタだね。

S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル(玉井浩訳)
5.3.2 ナルポインタと不正ポインタ

によると、

「不正ポインタをNULLと比較したり,関数の実引数にしたり,その値を他のポインタに代入したり,といった不正ポインタ使用の効果は規格Cでは未定義である.」

つまり、freeで領域解放した後のポインタはポインタ値のNULL比較すらしてはいけないということになる。
    /* これは未定義動作! */
    free(p);
    if (p == NULL)

だから、freeで解放した直後にポインタにnullを代入しておくのは良い作法ということになる。
    /* これは安全 */
    free(p);
    p = null;
    if (p == NULL)

配列領域の参照

配列参照に関してはJPCERTに以下の項目がある。

JPCERT ARR30-C. 境界外を指すポインタや配列添字を生成したり使用したりしない
配列オブジェクトおよび整数型内またはこれらをちょうど超えた位置を指すポインタの加算または減算が、同じ配列オブジェクト内またはそれをちょうど超えた位置を指さないという結果を生み出す。

となっている。でも、なんで

「またはこれらをちょうど超えた位置を指すポインタ」
なんて言い回しになっているんだろう。
これは確保したメモリ領域の1個先だけはアドレスを見てもいいってことなんだね。

以下のように領域全体をループしてスキャンするような処理では p + size のアドレスは見てもいい。
int loop(int *p, size_t size) {
    for (int *pi = p; pi < p + size; pi++)
        /* なにか処理 */
    }

でも、もちろん *(p + size) は見ちゃダメだよ。