対象 | C言語初心者 |
記事概要 | C言語のポインタについて記載しています。 |
筆者 | Zuu [ ] C言語歴20年以上。 大手電機メーカーでの製品開発およびマネジメント経験がある現役エンジニアです。 プロジェクトマネージャ、システムアーキテクトなどの資格を保有しています。 |
はじめに
ポインタは、「変数」の一種です。変数についての理解が不十分だと感じている場合は、まずは変数について学びましょう。
本サイトでは、以下の記事で変数について説明しています。
よろしければご覧ください。
ポインタとは?
ポインタ (英: pointer) とは、コンピュータプログラミングにおいて、変数や関数などが置かれたメインメモリ上の番地(メモリアドレス)などを格納する特殊な変数のこと。
ポインタを一言で表すと、上記引用の通りなのですが、おそらくこれだけでは伝わらないと思いますので、もう少しわかりやすく説明していきます。
名前以外で変数や関数を識別する手段
今まで変数や関数は、名前(変数名・関数名)で識別してきました。名前で識別することは、プログラミングをしている人間にとっては非常に自然でわかりやすいですよね。
それに対して、ポインタは「名前以外で変数や関数を識別する手段」となります。ポインタ(メモリアドレス)は、人間よりコンピュータ向けの識別手段だと思ってください。ゆえに、人間にとっては少しわかりにくいのです。
メモリアドレス/番地とは、メモリ上のどこにあるかを示す情報です。碁盤の目のように区切られた中で、位置を表すものと考えていただければ問題ありません。
図で表すと、以下のような感じですね。
まだポインタについてイメージがわかないという人は、タンスをイメージするとわかりやすいかなと思います。ポインタ(メモリアドレス/番地)は、タンスの1段目や2段目などの場所と同じ感じです。
また、どこに何をしまったかをわかりやすくするために引き出しに貼り付けたラベルが、変数や関数における名前に相当します。多くに人にとっては、このようなラベルがあるとわかりやすいですよね。
名前以外で識別する手段の必要性
名前による識別ができるのに、なぜ名前以外で識別する必要があるのかと疑問に思う方もいるでしょう。それは、名前で識別する手段だけでは問題があるケースがあるためなのです。
例えば、ローカル変数は別の関数から名前でアクセスすることはできません。ですが、別の関数の変数を書き換えたくなることはよくあります。
もちろん、上記の問題はグローバル変数を使えば解決できます。ただし、実際の開発の現場ではグローバル変数での解決は推奨されません。
推奨されない理由としては、「関数同士の依存度が上がってしまう」ということが挙げられます。ソフトウェア開発は、関数単位で再利用することが多いため、依存度が高い関数があると再利用のハードルが上がってしまい、敬遠されてしまいます。
C言語でのポインタの扱い方
宣言
ポインタは変数の一種なので、使う場合には宣言が必要です。宣言の際、型の後、もしくは変数名の前に「アスタリスク(*)」を付与することでポインタ変数として扱うことができるようになります。
当サイトでは、変数名の前に「アスタリスク(*)」を付与する方法をおすすめします。
1 2 3 |
int *i; char *c; float *f; |
同じ型のポインタ変数なら、1行で複数宣言することもできます。
※型の後に付与する方法だと複数宣言できません。
1 |
int *i, *j; |
代入
他の変数と同様に、ポインタ変数も代入することができます。ポインタ変数は、今までの変数と違い「メモリアドレス」を記憶する必要があるため、変数や関数のメモリアドレスを取得する必要があります。
変数のメモリアドレスを取得するには、変数名の前に「アンパサント(&)」を付与します。
1 2 3 4 |
int a; int *p; p = &a; |
ポインタ変数経由で変数にアクセスする
ポインタ変数に記憶されているメモリアドレスにある変数にアクセスするには、ポインタ変数名の前に「アスタリスク(*)」を付与します。
※宣言と同じ
※「int型」の「*p」という変数と見れば理解しやすいかもしれません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
int main(void) { int a; /* int型変数 名前a */ int *p; /* int型へのポインタ変数 名前p */ p = &a; /* aへのポインタをpに記憶 */ a = 123; printf("a = %d\n", a); /* aに記憶されている値を表示 */ printf("*p = %d\n", *p); /* pが指し示すint型変数(つまりa)に記憶されている値を表示 */ *p = 1234; /* pが指し示すint型変数(つまりa)の値を変更 */ printf("a = %d\n", a); /* aに記憶されている値を表示 */ printf("*p = %d\n", *p); /* pが指し示すint型変数(つまりa)に記憶されている値を表示 */ return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void func(int a, int b, int *result) { *result = a + b; /* aおよびbの和を、resultが指し示すint型変数に格納 */ } int main(void) { int r; /* int型変数 名前r */ int *p; /* int型へのポインタ変数 名前p */ p = &r; /* rへのポインタをpに記憶 */ func(10, 5, p); /* 関数func()の結果をpが指し示すint型変数(つまりr)に格納 */ printf("r = %d\n", r); /* rに記憶されている値を表示 */ printf("*p = %d\n", *p); /* pが指し示すint型変数(つまりr)に記憶されている値を表示 */ } |
その他に覚えておきたいこと
配列に対する使い方
ポインタは配列に対しても使うことができます。基本的には通常の型と同じように使用することができますが、少し特殊な使い方があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
int main(void) { int a[5] = {1, 2, 3, 4, 5}; /* int型配列 名前a */ int *p; /* int型へのポインタ変数 名前p */ p = &a[2]; /* 配列aの3番目の要素へのポインタをpに記憶 */ a[2] = 123; printf("a[2] = %d\n", a[2]); printf("*p = %d\n", *p); p = a; /* 配列の先頭の場合は、&と[0]を省略可能 */ *(p + 2) = 1234; /* ポインタ変数に対して加減算できる */ printf("a[2] = %d\n", a[2]); printf("*(p + 2) = %d\n", *(p + 2)); return 0; } |
構造体に対する使い方
ポインタは構造体に対しても使うことができます。以下の記事で説明していますので、よろしければご覧ください。
関数に対する使い方
ポインタは変数のみでなく、関数に対しても使うことができます。以下の記事で説明していますので、よろしければご覧ください。
直接メモリアドレスを指定する方法
オペレーティングシステムやデバイスドライバの開発などの場合に、ポインタ変数に直接メモリアドレスを代入することがあります。
ポインタ変数に直接メモリアドレスを代入する方法については、別の記事で紹介します。
しばらくお待ちください。
まとめ
- 変数および関数に対する、名前以外での識別手段。
- 文法が特殊。
- ポインタ変数の宣言は、型に「アスタリスク(*)」を付ける。
- 変数へのポインタを取得する場合は、変数に「アンパサント(&)」を付ける。
- 変数へのポインタから変数の値を取得する場合は、ポインタ変数に「アスタリスク(*)」を付ける。
- 関数へのポインタは、「アンパサント(&)」はなくても良い。
コメント