C言語規約シリーズです。今回はポインタの注意すべき点についてです。


ヌルポインタの表現には0でなく"NULL"を使用する

コンパイラは、値が0のポインタを「ヌルポインタ」と解釈するので、通常は、定数0とNULLとを区別しなくても問題無く動作します。 ですが、コンパイラがポインタ型なのか整数型なのか判断できないときは、正常に動作しない可能性があります。特に可変引数関数の場合、コンパイラが型チェックをできないので注意が必要です。 表現上もまぎらわしいので、ヌルポインタには0でなくNULLを使用してください。

違反コード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 可変引数関数.
// 最後の引数は番兵としてヌルポインタを渡す仕様とする.

void va_list_func(char *string, ... );

void func_NG(void)

{
    // NG. 最後の番兵のヌルポインタを0で渡しているので、
    //     正常に動作しない可能性がある

    va_list_func("Hello,", "world.\n", 0);
}

適合コード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 可変引数関数.
// 最後の引数は番兵としてヌルポインタを渡す仕様とする.
void va_list_func(char *string, ... );

void func_OK(void)

{
    // OK. NULLで渡しているので、コンパイラもポインタだと理解できる。

    va_list_func("Hello,", "world.\n", NULL);
}

ポインタはNULLでないことを確認してから参照する

NULLポインタへのアクセスはしないでください。 どこを向いているかわからないポインタにアクセスするときは、必ずNULLでないか確認してください。(特に関数の引数でポインタをもらう場合。)

違反コード

1
2
3
4
void func_NG(int *p)
{
   *p = 0xff;  // pがNULLを指しているかもしれない.
}

適合コード

1
2
3
4
5
6
void func_OK(int *p)

{
    if (p != NULL)
        *p = 0xff;
}

ポインタをより小さい型へのポインタへキャストしない

異なるサイズの型には異なるアラインメントが適用される可能性があります。 例えば処理系によっては、int型(4Byte)は4Byteアラインメントされ、char型(1Byte)は1Byteアラインメントされます。 このとき、奇数バイトに配置されたchar型へのポインタ変数をlong型へのポインタ変数にキャストすると、long型は4Byteにアラインメントされている必要があるため正しくアクセスできません。大きい型へのポインタを小さい型へのポインタにキャストしないでください。

違反コード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// longへのポインタ型を引数とする関数.
void func_fuga(long *long_table);

void func_NG(void)

{
    char char_hoge[DATA_LENGTH];

    // NG.  もしchar_hogeが奇数バイトに置かれていた場合、正常に動作しない。
    func_fuga((long *) char_hoge);

    return;
}

適合コード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// longへのポインタ型を引数とする関数.
void func_fuga(long *long_table);

void func_OK(void)

{
    long long_hoge[DATA_LENGTH / sizeof(long)];

    // OK.
    func_fuga(long_hoge);

    return;
}

関数のアドレスを取得するときはアドレス演算子"&“をつける

関数のアドレスを取得するときは、括弧をつけずに単に関数名を書けば取得できます。 しかしそれだと関数コールの記述ミスなのかどうかがわかりづらいので、関数のアドレスを取得するときも通常の変数と同じように&をつけてください。

違反コード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <string.h>

// 関数ポインタ変数.
char* (*func_ptr)(char *buf1, const char *buf2);

void func_NG(void)

{
    // NG, strcpyのコールミスなのか、strcpyのアドレスを取得しているのかわかりにくい.
    func_ptr = strcpy;

    // NG, func_ptrという関数自体をコールしているのか,
    //     func_ptrというポインタ変数を介して関数コールしているのかわかりにくい.
    func_ptr(s1, s2);
}

適合コード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 関数ポインタ変数.
int (*func_ptr)(void);

void func_OK(void)

{
    // OK, strcpyのアドレスを取得しているのが明示的にわかる.
    func_ptr = &callback_func;

    // OK,  func_ptrというポインタ変数を介して関数コールしているのが明確.
    (*func_ptr)();
}

以上、ポインタについてのコーディング規約でした!