C言語ポインタ実践ガイド:型、const、演算、安全性を完全理解

ポインタの型が示す意味

アドレスを格納する変数は全て「ポインタ」と呼ばれますが、なぜ int*char* など型を区別するのでしょうか。型の違いが与える影響を実験で確かめます。

型によるメモリアクセス幅の違い

次の2パターンで4バイト整数を書き換えた場合の挙動を比較してください。

int main(void) {
    unsigned value = 0xAABBCCDD;
    int   *pi = &value;   // 4バイト単位
    char  *pc = (char*)&value; // 1バイト単位

    *pi = 0;              // 0x00000000
    // *pc = 0;           // 0xAABBCC00(下位1バイトのみ)
    return 0;
}

int* は4バイトまとめて、char* は1バイトだけを書き換えるため、結果が異なります。

ポインタ演算での歩幅差

int main(void) {
    double arr[3] = {1.1, 2.2, 3.3};
    double *pd = arr;
    char   *pb = (char*)arr;

    printf("pd   = %p\n", (void*)pd);
    printf("pd+1 = %p\n", (void*)(pd + 1)); // 8バイト進む
    printf("pb   = %p\n", (void*)pb);
    printf("pb+1 = %p\n", (void*)(pb + 1)); // 1バイト進む
    return 0;
}

ポインタの型が決める「歩幅」は、配列走査やバッファ操作で重要になります。

汎用ポインタ void*

void* はどんな型のアドレスも受け取れますが、次の2点に注意が必要です。

  • 間接参照(*ptr)不可
  • ポインタ演算不可
void generic_swap(void *a, void *b, size_t size) {
    unsigned char tmp[size];
    memcpy(tmp, a, size);
    memcpy(a, b, size);
    memcpy(b, tmp, size);
}

このように汎用関数の引数で活用できます。

const による保護レベル

const の位置で保護対象が変わります。

書式保護対象
const int *p指す値*p = 5; NG
int *const pポインタ自身p = &x; NG
const int *const p両方両方 NG
void read_only(const int *src) {
    // *src = 10; // コンパイルエラー
}

ポインタ演算テクニック

配列走査

int sum_array(const int *begin, const int *end) {
    int total = 0;
    for (const int *p = begin; p != end; ++p)
        total += *p;
    return total;
}

ポインタ差による距離計算

size_t str_len(const char *s) {
    const char *start = s;
    while (*s) ++s;
    return s - start;
}

同一配列内でのみ意味を持つ演算です。

危険な「ダングリングポインタ」と対策

典型的な発生ケース

  1. 未初期化
    int *p; *p = 3; // 未定義動作
  2. 範囲外アクセス
    int buf[4]; for (int *q = buf; q < buf + 8; ++q) *q = 0;
  3. 寿命切れ参照
    int *get_local(void) { int tmp = 42; return &tmp; }

安全なコーディングルール

  • 即座に初期化:int *p = NULL;
  • 範囲チェックを常に実施
  • 不要になったら NULL クリア
  • ローカル変数のアドレスを返さない
int main(void) {
    int data[5] = {0};
    int *cursor = data;

    for (size_t i = 0; i < sizeof(data)/sizeof(data[0]); ++i)
        *cursor++ = (int)i;

    cursor = NULL; // 使用後に無効化
    return 0;
}

実行時チェック assert

デバッグ時に契約を検証するマクロです。

#include <assert.h>

void array_set(int *arr, size_t idx, int val, size_t max) {
    assert(arr != NULL);
    assert(idx < max);
    arr[idx] = val;
}

リリースビルドでは NDEBUG を定義して無効化できます。

タグ: C言語 ポインタ const修飾子 メモリ管理 assertマクロ

8月5日 23:07 投稿