お役立ち情報

C言語のポインタについてわかりやすく解説してみた

C言語のポインタについて

ポインタの概念はC言語特有のものではありませんが、現在主流のプログラミング言語のなかでプログラマーがポインタを意識してプログラミングを行うのはC言語くらいでしょう。

しかし、C言語でポインタの学習をしておくとプログラミング全般で役立ちます。というのも、そもそもポインタとはメモリ上のアドレスを指し示すものです。プログラミングによって記述された処理はすべてメモリ上のどこか、つまりアドレス上で実行されています。

たとえば、変数を用意すればそれもどこかのアドレスに配置されています。現在主流のJavaやPHPなどでは、記述した処理がメモリ上のどこで実行されたか意識することはないでしょう。

C言語でも厳密にはメモリ上のどこかは意識しないのですが、ポインタを用意することで、アドレスを保持することができます。詳細は後述しますが、情報処理試験を受けたことのある方はそれをイメージするとわかりやすいです。

連想配列などでキーと値をセットで格納する概念に少し近いのですが、ポインタはたとえば変数の場所をそのまま保持するという点で違いがあります。

ポインタのサンプルコード

ポインタの概念は上記の通りで、アドレスを格納する変数です。実際のソースコードで確認するとわかりやすいのですが、具体的には以下のようになります。

#include <stdio.h>

int main(void) {
     int num = 1; // int型変数
     int *p_num; // int型ポインタ変数
     p_num = &num; // ポインタ変数p_numに変数numアドレスを代入
     printf("int型変数numの値:%d\n", num);
     printf("int型ポインタ変数:%p\n", p_num);
     return 0;
}

上記のソースコードを実行すると、以下のようにコンソール出力されます。

int型変数numの値:1
int型ポインタ変数:0x7ffd0a0ecc6

注意点としては、まずポインタ変数を宣言する際は、「*」を前に付けます。上記のソースコードでは、「*p_num」と記述しています。次に、ポインタ変数にアドレスを格納する際は変数名の前に「&」を付けます。

「&」を付けることで、その変数のアドレスを指し示します。この二つがわかれば、上記のソースコードは非常にシンプルで簡単なものかと思います。もう一点知っておいた方が良いこととしては、「*p_num」というようにアスタリスクを付けることで、出力処理等の処理を行う際にポインタの先の値に対して処理を加えることが可能です。

逆に値の格納された変数に「&」を付けるとその変数のアドレスを示すことになります。上記の例で出力してみるとわかりやすいでしょう。

#include <stdio.h>

int main(void) {
     int num = 1; // int型変数
     int *p_num; // int型ポインタ変数
     p_num = &num; // ポインタ変数p_numに変数numアドレスを代入
     printf("int型変数numのアドレス:%p\n", &num);
     printf("int型変数numのアドレス先の値:%d\n", *(&num));
     printf("int型ポインタ変数p_numの参照先の値:%d\n", *p_num);
     return 0;
}

上記のソースコードを実行すると、以下のようにコンソール出力されます。

int型変数numのアドレス:0x7ffd0a0ecc64
int型変数numのアドレス先の値:1
int型ポインタ変数p_numの参照先の値:1

出力処理以外は最初のソースコードと同じです。「&num」は変数の格納先のアドレス、「*(&num)」は変数の格納先のアドレスのなかの値、というややこしい書き方ですが、最終的に値になります。あまりこのような書き方をすることはないでしょう。最後に、「*p_num」はポインタの元の値になります。

ポインタ変数に付けるのか普通の変数に付けるのか混乱することがあるかもしれませんが、「&」を付けるとアドレス、「*」を付けると値、と覚えておくと良いかと思います。そのように覚えておけば、ポインタ変数にそのまま「&」を付けても意味がないことや、値の格納された変数に「*」を付けても意味がないことが自然にわかります。

ポインタの演算で配列の要素を操作する

ポインタは特に配列を操作するのに便利です。なぜなら、配列の要素格納先はアドレスが連続しているからです。

#include <stdio.h>

int main(void) {
     int intArray[] = {0, 1, 2, 3, 4};
     int *p_int; // int型ポインタ変数
     // ポインタにアドレスを代入
     p_int = intArray;

     printf("int型ポインタ変数p_int:%p, アドレス先の値:%d\n", p_int, *p_int);
     printf("int型配列要素intArray [0]のアドレス:%p, アドレス先の値:%d\n", & intArray r[0], *(& intArray [0]));
     printf("int型ポインタ変数(p_int + 1):%p, アドレス先の値:%d\n", p_int + 1, *(p_int + 1));
     printf("int型配列要素intArray [1]のアドレス:%p, アドレス先の値:%d\n", & intArray [1], *(& intArray [1]));

     return 0;
}

int型ポインタ変数p_int:0x7fff3e4c1100, アドレス先の値:0
int型配列要素intArray [0]のアドレス:0x7fff3e4c1100, アドレス先の値:0
int型ポインタ変数(p_int + 1):0x7fff3e4c1104, アドレス先の値:1
int型配列要素intArray [1]のアドレス:0x7fff3e4c1104, アドレス先の値:1

配列に対してポインタ変数を設定した場合、自動的に配列の先頭のアドレスとポインタが一致します。int型の配列は、ポインタ変数を1加算すると4増えます。なぜなら、int型の変数1個で4バイトのアドレスを使用しているからです。

ポインタのポインタ

ポインタは上記の通り変数のアドレスを格納するための変数ですが、そのポインタ自体もどのかのアドレスに格納されます。つまり、ポインタが格納されたアドレスをさらにポインタ変数で指定することも可能なのです。

具体的には以下のサンプルコードのようになります。

#include <stdio.h>

int main(void) {
     int num = 1; // int型変数
     int *p_num; // int型ポインタ変数
     p_num = &num; // ポインタ変数p_numに変数numアドレスを代入

     int **pp_num; // int型ポインタ変数のポインタ
     pp_num = &p_num; // ポインタ変数のポインタpp_numにポインタ変数p_numのアドレスを代入

     printf("int型変数numの値:%d\n", num);
     printf("int型ポインタ変数p_num:%p\n", p_num);
     printf("int型ポインタ変数のポインタpp_num:%p\n", pp_num);

     return 0;
}

上記のソースコードを実行すると以下のようにコンソール出力されます。

int型変数numの値:1
int型ポインタ変数p_num:0x7ffef434156c
int型ポインタ変数のポインタpp_num:0x7ffef43415

混乱してきそうな感じがしますが、一つづつ落ち着いてトレースすれば理屈としては単純です。一つ目のポインタの宣言から格納までの処理は今までのソースコードと同じで、二個目のポインタに格納する際に一つ目のポインタを普通の変数のように扱っているだけです。

ポインタの説明は以上のようになりますが、最初は混乱するかもしれません。しかし何度か使用していれば慣れるので、最初からすべて理解しようとするよりは、だいたいの概要を掴めば最初は良いかと思います。

キャリアカウンセリングのプロとして
あなたに合った案件をご案内します。

まずはお気軽にお問い合わせください!

イメージ