ポインタ (プログラミング)

実際の例

一般的なC言語のソースコードでは、ポインタが指している領域の値を参照する間接演算子 (indirection operator) "*"と、アドレス演算子 (address operator) "&"を用いて記述される。未初期化のポインタ変数は、不定の領域を指している。しかし、その場合、「未初期化状態」と「有効な領域を指している状態」の区別がつかない。そのため、Null(ヌル)値を代入することによって、ポインタが無効な領域を指していることを明示する必要がある。

関数ポインタ

上述の通り、C言語では関数を指すポインタ (pointer to function / function pointer) を作成することができる。

ポインタ演算

記憶域 (メモリ) のアドレス空間は、1次元空間である。たとえば32ビットシステムのアドレス空間は、16進数表記で 0x00000000 から 0xFFFFFFFF (4GiB-1) までの整数値が有効な範囲である。ポインタはこのアドレス空間を抽象化し、メモリ上の任意位置のデータ（オブジェクト）にアクセスするためのデータ型である。なお、プロセスのアドレス空間は物理メモリ上のアドレス（物理アドレス）に直接対応するとは限らない。通例、オペレーティングシステムによって物理メモリは抽象化され、プロセスごとに仮想アドレス空間が割り当てられ、プロセスごとの仮想アドレス空間におけるメモリの読み書き処理はOSによって物理アドレスに対する処理に変換される（メモリマッピング）。
ポインタに対する算術演算は、加減算のみが許可される。ポインタに加減算すると、そのポインタが指すデータ型のサイズに比例したオフセットがアドレスに加減算されることになる。これは配列の添え字演算子が、ポインタの加減算とデリファレンスの糖衣構文であることからも自明である。つまり、ある型Tへのポインタの加減算は、メモリ全体をTの配列とみなして、インデックスを増減していることに他ならない。
int* p = NULL;printf("Size of int = %d\n", (int)sizeof(int));printf("%p\n", p);p++; /* sizeof(int) * 1 だけアドレスが加算される。 */printf("%p\n", p);p += 10; /* sizeof(int) * 10 だけアドレスが加算される。 */printf("%p\n", p);p--; /* sizeof(int) * 1 だけアドレスが減算される。 */printf("%p\n", p);
バイト単位のアドレッシングが必要な場合、char / signed char / unsigned charへのポインタを利用する。これらの型はサイズが1であることが規格で保証されているため、これらの型へのポインタに対する加減算操作はバイト単位のアドレッシングとなる。
なお、汎用ポインタvoid*は型が不明のため、アドレスの加減算を行なうことができないが、GCC拡張の非標準動作ではvoid型のサイズに1を割り当てるため、加減算が可能となっている。
void* pv = NULL;pv++; /* C/C++標準規格に準拠した環境ではコンパイルエラーとなる。 */printf("%p\n", pv);
ポインタ型のサイズは処理系依存であり、通例ターゲットとするプロセッサアーキテクチャのレジスタ幅と同じサイズを持つ。
オフセット計算などでポインタ間の差を得る必要がある場合、<stddef.h>で定義されているptrdiff_t型を用いる。ポインタ（アドレス値）を整数型に代入する必要がある場合、<stdint.h>で定義されているポインタ互換の整数型intptr_tやuintptr_tを用いる（C99およびC++11で標準化されている）。なお、C/C++ではintやlongなどの組み込み整数型のサイズもまた処理系依存である。移植性を損なうため、ポインタを扱う目的で組み込み整数型を直接使用してはならない。
// C99 の例。#include <stdio.h>#include <stddef.h>#include <stdint.h>...int a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };int* p1 = &a[0]; // または (a + 0)int* p2 = &a[9]; // または (a + 9)printf("p1 = %p, p2 = %p\n", p1, p2);ptrdiff_t difference = p2 - p1;printf("Difference as pointer = %td\n", difference); // 9intptr_t i1 = (intptr_t)p1;intptr_t i2 = (intptr_t)p2;printf("Difference in bytes = %jd\n", (intmax_t)(i2 - i1)); // sizeof(int) * 9

問題点

ポインタには不正な領域を示しうるという問題がある。たとえば、近年セキュリティ上で問題となっているバッファオーバーランの原因の多くは、ポインタ演算のエラーで起こる不正領域の書き換えによるものである。また、「オブジェクトそのものに対する操作」と「オブジェクトの位置に対する操作」が混在することは、プログラマの混乱を招きやすい。このような問題もあって、JavaやC#など、C言語よりも新しい後発のプログラミング言語では、言語レベルでのポインタ機能は、排除されるか制限される方向にある。
しかし、プログラマーに直接ポインタ操作を許可していない言語でも、ポインタ概念は存在する。たとえば、配列中にオブジェクトを格納し、それを要素のインデックスで参照すれば、これは「ポインタ概念」を活用していることになる。したがって、配列の要素数を超えた領域をアクセスすれば、エラーが発生する。しかし、配列へのインデックスアクセスを完全に排除してしまうと、その言語の制限が厳しくなり、単純な動作を簡易に記述できる領域を狭めてしまう（言語の表現力が低下する）。このように、ポインタには危険性があるが、プログラミングをするうえでは、非常に強力なテクニックである。また、C言語でマイコンの周辺デバイスを制御する場合、メモリバス上の特定のアドレスにあるレジスタに値を読み書きする必要があるため、必須のテクニックとなる。
一方、関数型言語などの発展により、ポインタの必要性は、今後減少する可能性が考えられる。また、データベース領域では、SQLのように関係式からデータを導き出し情報の位置を抽象化する概念が古くからあり、こちらもプログラミングパラダイムに影響を与えることが考えられる。

C++における参照の例

int n = 5;int& n2 = n; // n2をnへの参照で初期化する
この場合変数n2はnを参照している。n2とnはオブジェクトを共有しているのでn2と呼んでもnと呼んでも同じものを表す。すなわち変数nにエイリアス（別名）n2が付いたことになる。
ポインタは未初期化の状態や、何も参照していない状態（ヌルポインタ）が許可されるが、C++における参照は必ず初期化が必要となり、何も参照していない状態は許可されない。ただし、ダングリングポインタと同様、破棄された領域への参照は不正となる。
int& foo() {
    int a = 0;
    return a; // 不正。制御が関数呼び出し元に戻った時点で a は破棄されている。}