ポインタは「アドレスを格納する変数」である。データそのものではなく、データが置かれているメモリ上の位置(アドレス)を扱う。配列名が先頭アドレスを表すこと、*(p+1) と (*p+1) の違い、多重ポインタ int ** まで、2級で最も差がつく単元である。
*p=中身、と説明できる ② &(アドレス演算子)と *(宣言時/間接参照の2用法)を区別できる ③ 配列名=先頭アドレス、要素番号=先頭からの距離が分かる ④ *(p+1) と (*p+1) の違いを言える ⑤ 多重ポインタ int **pp とポインタ配列 char *[] が読める。
「ポインタ」は指し棒をイメージすると分かりやすい。ポインタは「データそのもの」ではなく、データが格納されている「アドレス」を扱う。
変数や配列はメモリ上に配置される。メモリ上に置かれるということは、そこには必ず「アドレス」が存在する。C言語ではこのアドレスを特殊な変数に格納し、そのアドレスが指す先のデータを参照できる。これが「ポインタ」の仕組みである。
&ポインタ変数とは、他の変数やデータの「アドレス」を格納できる変数である。データそのものではなく、メモリ上の位置を操作できる。
int num = 3;
int *p;
p = # /* num のアドレスをポインタ変数 p に格納 */
ここで p は num のアドレスを保持し、*p で num の値を参照できる。
&&(アンパサンド)を変数の前につけると、「その変数が格納されたメモリアドレス」になる。
int num = 3;
int *p = # /* &num = num のアドレス。それを p に代入 */
int *p = # で p に入るのは num のアドレス(num の値「3」ではない)。
* の2つの意味アスタリスク * には2つの使い方がある。同じ記号なので混同しやすいが、出てくる場所で見分ける。
| 用法 | 意味 | 例 |
|---|---|---|
型修飾子としての * | 「ポインタ変数」を宣言する(〜へのポインタ型) | int *p; |
間接参照演算子としての *(デリファレンス) | ポインタが指すアドレスの「中身(値)」を取り出す | *p |
*)int num = 3;
int *p = # /* num のアドレスをポインタ p に代入 */
p は「整数型データへのポインタ」。*)int num = 10;
int *p = # /* num のアドレスを p に格納 */
printf("%d¥n", *p); /* 10 を出力(p が指す先の中身) */
*p は、ポインタ p が指しているアドレスのデータ(中身)を表す。p(ポインタ変数そのもの)= アドレス=うつわ*p(間接参照)= うつわの中身
宣言 int num = 3; int *p = # はメモリ上で次のようになる。
num は番地 0xFFFF1230 に値 3、p 自身は番地 0xFFFF4560 に置かれ、その中身は num の番地 0xFFFF1230。つまり p の中身は 0xFFFF1230(num のアドレス)、*p はそのアドレスの中身 3 になる。
int *p; /* 整数型へのポインタ */
char *str; /* 文字型へのポインタ */
int x = 20;
int *p = &x; /* x のアドレスを p に格納 */
p は x のアドレスを持つ。int *、文字型を指すなら char *。*p は x の値(20)を参照する。宣言 int x = 20; int *p = &x;
x は番地 0xFFFF1200 に値 20、p 自身は番地 0xFFFF1250 に置かれ、その中身は x の番地 0xFFFF1200。int *p; だけ)で *p を読み書きすると、どこを指しているか分からない(未初期化)ため未定義動作になる。必ず &変数 などで初期化してから使う。
配列とアドレスは密接な関係がある。最重要ルールは 「配列名は、その配列の先頭アドレス」 である。
int arr[3] = {1, 2, 3};
printf("%p¥n", arr); /* 配列の先頭アドレス */
printf("%p¥n", &arr[0]); /* 同じ値(先頭要素のアドレス) */
arr = 先頭 arr[0] のアドレス 0xFFFF1300。int は4バイトなのでアドレスは4ずつ進む。arr はアドレス値 0xFFFF1300 を表す。arr と &arr[0] は同じ(どちらも先頭アドレス)。
要素番号は、先頭アドレスからの「距離」である。
int arr[3] = {1, 2, 3};
printf("%p¥n", &arr[1]); /* 先頭から1つ分進んだアドレス = 0xFFFF1304 */
printf("%d¥n", arr[1]); /* 先頭から1つ分進んだアドレスの中身 = 2 */
int 型を4バイトとすると、arr の各要素は4バイト。要素を1つ先に進めると、アドレスは4つずつ飛ぶ。
*(p+1) と (*p+1) の違い)アドレスに対して +1 すると、そのデータ型のバイト数だけ加算されたアドレスを自動計算する(int * なら +4 バイト)。
int arr[3] = {10, 20, 30};
int *p = arr;
printf("%d¥n", *(p + 1)); /* 20 を出力 */
*(p+1) は arr+1 の中身 = 20。順を追って整理する。
arr = 0xFFFF1400、arr+1 = 0xFFFF1404、arr+2 = 0xFFFF1408(いずれもアドレス)*arr = 10、*(arr+1) = 20、*(arr+2) = 30(* で中身になる)p + 1 は p の次の要素を指す = 0xFFFF1404*(p + 1) は p の次の要素の中身を取得する = 20(*p + 1) は p の中身(10)を先に取り出して +1 したもの = 11*(p + 1) … アドレスを +1 してから中身 → arr[1] = 20(*p + 1) … 中身(arr[0]=10)を取り出してから +1 → 11
データに対する演算(参照した「値」に対して演算する):
int x = 5;
int *p = &x;
(*p)++; /* x の値が 6 になる(中身を取り出して +1) */
ポインタに対する演算(ポインタ自体を動かすと次のメモリ位置を指す):
int arr[3] = {10, 20, 30};
int *p = arr;
p++; /* 次の要素を指す */
printf("%d¥n", *p); /* 20 */
混乱しやすい例(アドレスと値の違い):
int arr[3] = {1, 2, 3};
int *p = arr;
printf("%p¥n", p); /* アドレスを出力 */
printf("%d¥n", *p); /* 値(1)を出力 */
printf("%d¥n", *(p+1)); /* 次の値(2)を出力 */
配列名が先頭アドレスである性質は、文字列(char 配列)でも同じ。ポインタ経由で中身を書き換えることもできる。
#include <stdio.h>
int main(void)
{
char str[] = "Hello";
char *p = str;
*(p + 1) = 'a'; /* 2文字目(index 1)を 'a' に変更 */
printf("%s¥n", str); /* 出力: Hallo */
return 0;
}
str の内容 "Hello" の e(index 1)を a に書き換えるので、結果は Hallo になる。
char *p = str; は「p に str の先頭アドレスを入れる」。*(p+1) は str の2文字目そのもの(同じメモリ)なので、代入すると元の str が変わる。
int **)ポインタが別のポインタ変数を指す場合、この関係を多重ポインタと呼ぶ。多重ポインタはポインタ自体のアドレスを保持し、間接的にデータを参照する。
int a = 10;
int *p = &a; /* p は a のアドレスを保持するポインタ */
int **pp = &p; /* pp は p のアドレスを保持するポインタ(多重ポインタ) */
| 変数 | 値 | 説明 |
|---|---|---|
a | 10 | 通常の整数型変数の値 |
p | &a | a のアドレスを保持するポインタ |
pp | &p | p のアドレスを保持するポインタ(多重ポインタ) |
#include <stdio.h>
int main(void)
{
int a = 10;
int *p = &a; /* p は a を指す */
int **pp = &p; /* pp は p を指す */
/* 値の出力 */
printf("a: %d¥n", a); /* 直接アクセス → 10 */
printf("*p: %d¥n", *p); /* p を介してアクセス → 10 */
printf("**pp: %d¥n", **pp); /* pp を介して間接アクセス → 10 */
/* アドレスの出力 */
printf("&a: %p¥n", (void *)&a); /* a のアドレス */
printf("p: %p¥n", (void *)p); /* p に格納されているアドレス */
printf("*pp: %p¥n", (void *)*pp); /* pp を介して得た p の中身(=a のアドレス) */
return 0;
}
a: 10
*p: 10
**pp: 10
&a: 0x7ffee7e5a97c
p: 0x7ffee7e5a97c
*pp: 0x7ffee7e5a97c
pp は p のアドレス、*pp は p の中身(=a のアドレス)、**pp はそのまた中身(=a の値 10)。* を1つ外すごとに「1段だけ中身に潜る」と覚える。
char *[])文字列(の先頭アドレス)をまとめて持つにはポインタ配列 char *[] を使う。各要素が「文字列の先頭アドレス」になる。配列名は先頭を指すので char ** で受けられる。
#include <stdio.h>
int main(void)
{
char *instruments[] = {"violin", "cello", "flute", "horn"};
char **ptr = instruments; /* 配列の先頭アドレスを指すポインタ */
int i;
for (i = 0; i < 4; i++) {
printf("%s¥n", *(ptr + i)); /* 各要素(文字列)を出力 */
}
return 0;
}
ptr は instruments 配列の先頭を指し、ポインタ演算 *(ptr + i) で各文字列を順に取り出す。
同じ考え方の例(こちらも char ** でポインタ配列の先頭を受ける):
#include <stdio.h>
int main(void)
{
char *strings[] = {"apple", "banana", "cherry"};
char **ptr = strings; /* ポインタ配列の先頭を指す */
int i;
for (i = 0; i < 3; i++) {
printf("%s¥n", *(ptr + i));
}
return 0;
}
多重ポインタは動的メモリ管理や2次元配列の操作にも使える。「ポインタの配列」を確保し、その各要素にさらに行を確保する。やや難しいので、まずは「int ** で行を、int * で各行を確保する」という形だけ押さえればよい。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int rows = 2, cols = 3;
int **matrix;
int i, j;
matrix = (int **)malloc(rows * sizeof(int *)); /* 行ぶんのポインタを確保 */
for (i = 0; i < rows; i++) {
matrix[i] = (int *)malloc(cols * sizeof(int)); /* 各行を確保 */
for (j = 0; j < cols; j++) {
matrix[i][j] = i + j;
}
}
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
printf("%d ", matrix[i][j]);
}
printf("¥n");
}
for (i = 0; i < rows; i++) {
free(matrix[i]); /* 各行を解放 */
}
free(matrix); /* 行ポインタの配列を解放 */
return 0;
}
free で解放する(メモリリーク防止)。確保した順と逆順で、各行を解放してから matrix 本体を解放する。
free で解放する。
p はアドレス(器)、*p は中身。&=アドレス演算子。* は宣言時=型修飾子/式中=間接参照の2用法。arr = &arr[0])。要素番号=先頭からの距離。*(p+1)=次の要素の中身、(*p+1)=中身を取り出して +1。カッコで意味が変わる。int * なら4バイト)。int **pp:* を外すごとに1段ずつ中身へ。ポインタ配列 char *[] は char ** で受ける。