ポインタの型が示す意味
アドレスを格納する変数は全て「ポインタ」と呼ばれますが、なぜ 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;
}
同一配列内でのみ意味を持つ演算です。
危険な「ダングリングポインタ」と対策
典型的な発生ケース
- 未初期化
int *p; *p = 3; // 未定義動作 - 範囲外アクセス
int buf[4]; for (int *q = buf; q < buf + 8; ++q) *q = 0; - 寿命切れ参照
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 を定義して無効化できます。