ポインタの基礎
ポインタは住所のようなものです——住所そのものは家ではありませんが、それを通じてまさにその家を見つけられます。アドレスをマスターすれば、Cの最も強力な機能をマスターできます。
ポインタとは何か
メモリの各バイトには一意の番号が付いており、それをアドレスと呼びます。ポインタ変数は、アドレスを格納するために特別に設計された変数です。
int x = 42;
int *p = &x;
xは整数変数で、値は42&xはメモリ上のxのアドレスpはxのアドレスを格納するポインタ変数*pはアドレスを通じてxの値にアクセスし、それは42
ポインタ自体もメモリを占有します。32ビットシステムでは4バイト、64ビットシステムでは8バイトで、指している型に関係ありません。
アドレス演算子&
&は変数のアドレスを取得するために使います。
int a = 10;
printf("Value of a: %d\n", a);
printf("Address of a: %p\n", (void *)&a);
%p書式指定子はアドレスを出力します。実引数はvoid *にキャストしてください。
アドレスを取得できるもの:
- 変数:
&x - 配列要素:
&arr[3] - 構造体のメンバ:
&s.age
アドレスを取得できないもの:
- リテラル:
&10(不正) - register変数:
&r(rがregisterの場合) - 式の結果:
&(a+b)(不正)
間接演算子*
*はポインタが指すデータにアクセスするために使います。
int x = 42;
int *p = &x;
printf("%d\n", *p);
*p = 100;
printf("%d\n", x);
1つ目の出力は42、2つ目は100です。*p = 100はポインタを通じてxを変更しています。
*は「これはポインタである」を意味し、式の中の*は「間接参照(指している値を取得)」を意味します。文脈によって意味が異なります。
未初期化のポインタの間接参照は重大なエラーです。
int *p;
*p = 10;
pの値はランダムであり、ランダムなアドレスへの書き込みは他のデータを破損させたり、ただちにクラッシュさせたりする可能性があります。
NULLポインタ
NULLは「どこも指していない」ことを意味する特別なポインタ値です。<stddef.h>などの複数のヘッダで定義されており、値は通常0です。
int *p = NULL;
if (p == NULL) {
printf("Pointer does not point to a valid address\n");
}
NULLポインタの間接参照はセグメンテーション違反を引き起こし、プログラムがクラッシュします。したがって、ポインタを使う前にNULLかどうかを常に確認してください。
void safe_print(int *p) {
if (p != NULL) {
printf("%d\n", *p);
}
}
ポインタの型と歩幅
ポインタの型は、間接参照時に何バイト読み取るかと、ポインタ演算時に何バイト移動するか(歩幅)を決定します。
int a = 10;
double b = 3.14;
int *pi = &a;
double *pd = &b;
*piは4バイト読み取り(intのサイズ)、*pdは8バイト読み取り(doubleのサイズ)pi+1は4バイト進んで次のintへ、pd+1は8バイト進んで次のdoubleへ
ポインタの歩幅は指している型のサイズに等しいです。
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d\n", *p);
printf("%d\n", *(p + 1));
printf("%d\n", *(p + 3));
出力:10、20、40。p+1はアドレス値に1を加算するのではなく、sizeof(int)(4バイト)を加算し、次のint要素を指します。
ポインタの減算結果も要素単位で測られます。
int *q = &arr[4];
printf("%ld\n", (long)(q - p));
出力:4。2つのポインタの間に4つのint要素があることを意味します。
int *p = &d;(dはdouble)はコンパイラの警告が出ます。型の不一致は間接参照時に読み取るバイト数が間違う原因になります。
例
ポインタを使って2つの変数の値を交換します。
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int x = 10, y = 20;
printf("Before swap: x=%d, y=%d\n", x, y);
swap(&x, &y);
printf("After swap: x=%d, y=%d\n", x, y);
return 0;
}
Before swap: x=10, y=20
After swap: x=20, y=10
ここではアドレスを関数に渡し、関数は間接参照で元の変数を変更しています。これが「参照渡しのシミュレーション」の古典的な技法です。関数の仮引数はint *ポインタで、呼び出しでは&xを渡します。
ポインタとconst
constとポインタの組み合わせには2つの意味があります。
定数へのポインタ
const int *p = &x;
*p = 20;
不正。pを通じてデータを変更できません。しかしp自身は別の変数を指すことができます。
p = &y;
有効。
定数ポインタ
int * const p = &x;
p = &y;
不正。pは指す先を変更できません。しかしpを通じてデータを変更できます。
*p = 20;
有効。
記憶のコツ:constが*の左にあればデータを修飾し、右にあればポインタ自身を修飾します。
例
ポインタ走査で配列の合計を求めます。
#include <stdio.h>
int array_sum(const int *arr, int len) {
int sum = 0;
const int *end = arr + len;
while (arr < end) {
sum += *arr;
arr++;
}
return sum;
}
int main(void) {
int data[] = {3, 7, 1, 9, 5};
int total = array_sum(data, 5);
printf("Sum: %d\n", total);
return 0;
}
Sum: 25
仮引数const int *arrは、関数が配列の内容を変更しないことを示しています。ポインタendは停止位置を示し、添字の必要性をなくしています。
❓ よくある質問
int *pの*は型に属するのですか、変数に属するのですか?int* p, q;はpはintポインタ、qは普通のintとして宣言されます。1行につき1つのポインタ宣言を推奨します。📖 まとめ
- ポインタはアドレスを格納します。
&でアドレスを取得し、*で間接参照して値を得ます - 未初期化のポインタはワイルドポインタです。必ずNULLで初期化しましょう
- ポインタの型は歩幅と間接参照時の読み取りバイト数を決定します
- 関数にポインタを渡すことで、呼び出し元の変数を変更できます
constとポインタの組み合わせには2つの位置があり、データを修飾するかポインタ自身を修飾するかが異なります
📝 練習問題
- 商と剰余をポインタの仮引数で返す関数
void divide(int a, int b, int *quotient, int *remainder)を書いてください。 - int変数とdouble変数を定義し、それぞれ
int *とdouble *で指し、アドレスと値を出力して歩幅の違いを観察するプログラムを書いてください。 - 配列内の最大要素へのポインタを返す関数
int *find_max(int *arr, int len)を書いてください。



