導入:なぜポインタ演算が重要なのか
C++において、ポインタはメモリを直接扱う強力なツールです。特に配列を扱う際や、低レイヤーのメモリ操作を行う際、「ポインタに整数を足す」という操作が頻繁に登場します。この仕組みを正しく理解していないと、意図しないメモリ領域にアクセスしてしまい、プログラムがクラッシュする原因となります。今回は、ポインタ演算の基本的なルールとその背景にあるメモリの仕組みについて解説します。
基礎知識:ポインタ演算のルール
ポインタ演算において最も重要なのは、「ポインタへの加算は、単なる数値の加算ではない」という点です。
例えば、int型へのポインタに 1 を足した場合、アドレス値が物理的に 1 バイト増えるわけではありません。C++では、ポインタの型(T)に応じて、そのデータ型がメモリ上で占有するサイズ(sizeof(T))分だけアドレスが移動するよう設計されています。つまり、ポインタに n を足すと、実際には「n × sizeof(T)」バイト分だけアドレスが進むことになります。これにより、配列の次の要素を直感的に指し示すことができるのです。
実装:ポインタ演算の考え方
ポインタ p が型 T を指しているとき、式「p + n」は、「p が指す位置から、T 型のオブジェクト n 個分後ろのアドレス」を計算します。
・char 型(1バイト)のポインタの場合:1を加えると1バイト進む
・int 型(通常4バイト)のポインタの場合:1を加えると4バイト進む
・double 型(通常8バイト)のポインタの場合:1を加えると8バイト進む
このように、型情報がメモリ計算の基準となっているため、プログラマは「何バイト進めるか」を計算する必要がなく、純粋に「何個先の要素か」を指定するだけで安全に移動できる仕組みになっています。
サンプルプログラム
以下のコードを実行して、アドレスがどのように変化するかを確認してみてください。
include <iostream>
int main() {
int arr[] = {10, 20, 30};
int ptr = arr; // 配列の先頭アドレスを指す
for (int i = 0; i < 3; ++i) {
// ptr + i で配列の各要素にアクセス
// コンパイラが自動的に i sizeof(int) バイト分計算してくれる
std::cout << "インデックス " << i << " の値: " << (ptr + i) << std::endl;
// アドレスの表示(ポインタ演算の結果を確認)
std::cout << "アドレス: " << (ptr + i) << std::endl;
}
return 0;
}
応用・注意点:現場で陥りやすい罠
ポインタ演算を使用する際に最も注意すべき点は、「配列の境界外アクセス(バッファオーバーラン)」です。
ポインタ演算は非常に自由度が高いため、配列の末尾を超えてポインタを進めても、コンパイラは即座にエラーを出さないことが一般的です。存在しないメモリ領域を指したポインタに対して間接参照(演算子)を行うと、未定義動作を引き起こし、深刻なバグになります。
また、void型ポインタなど「サイズが確定しない型」へのポインタには演算ができないことも覚えておきましょう。現場では、可能な限り標準ライブラリの std::vector や std::array を使い、ポインタ演算を直接書く機会を減らすことが、安全なコードを書くための現代的なベストプラクティスです。

コメント