「ポインタ」の仕組みと使い方を学んでみる
今回は、自分にとってはかなり難解な「ポインタ」について学んでいきたいと思います。
前回、「C言語」を学んだ時には、ほとんど理解できなかったので、少しずつ「ポインタ」の仕組みについて勉強していきたいと思います。
とうとう「ポインタ」の学習までやってきてしまった・・・
「ポインタ」の仕組み
「ポインタ」が何を表すのかを学んでいくと、「変数などのデータのメモリアドレスを格納している変数」らしいのですが、「ポインタ」を利用する際には「*」の記号の「ポインタ演算子」を利用するそうです。
変数を作る時に、
データ型 *変数名;
のように書くと、「ポインタ」を作ることができます。
プログラムを書いてみると、
#include <stdio.h> int main(void) { // 変数を作る int num = 0; // ポインタ変数を作る int *pNum; //変数のアドレスを「ポインタ」に格納する pNum = # return 0; }
のようになります。
変数の「メモリアドレス」を取得するためには「アドレス演算子(&)」を利用すると取得することができます。
「ポインタ変数」に格納するのは「メモリアドレス」なのですが、意味がよく理解できなかったため、図にしてみたいと思います。
こんな感じかな。
変数の「メモリアドレス」が「ポインタ変数」に格納されているのがよくわかりますね♪
小学生のころに先生に「よくわからない部分は図にしてみるとわかりやすいよ。」とアドバイスをしてもらったことを思い出しました。
「ポインタ変数」を利用して値を格納することもできるみたいで、
#include <stdio.h> int main(void) { int num = 0; // ポインタ変数を作る int *pNum; //変数のアドレスを「ポインタ」に格納する pNum = # // ポインタを利用して変数に値を格納する *pNum = 1; printf("num=%d\n", num); return 0; }
のように、「ポインタ変数」の前に「ポインタ演算子(*)」を付けると変数に値を格納することができます。
このプログラムを実行してみると、
num=1
になり、「num」変数の初期値が「0」だったのですが、ポインタ変数への代入によって「1」に変更されていることがわかりますね。
使い方はわかったのですが、結局この機能はどういう風に利用できるのかがイメージができません。
どんな理由でどういう風に使われるのかを調べてみると、「メモリの有効利用」「処理の高速化」が主な理由みたいですが、「どのように」という具体的な内容を理解できるだけの知識が無いので、少しずつ勉強していきたいと思います。
「C言語」を学んでいると、「自分の学びたい内容」が高度で、「自分の知識が追い付かない」ということが多々ありますね(^^;)
さて、次はさらに複雑な・・・
ポインタのポインタの仕組み
一番初めにこの考え方について聞いたときにはサッパリわからなかったのですが、「ポインタのポインタ」という仕組みがあるそうです。
「ポインタ」の入っている変数の「メモリアドレス」を格納する変数のことを「ポインタのポインタ」というそうなのですが、言葉だけを聞いても全く意味がわかりませんでした。
「図」にしたらわかりやすくなるかと、「ポインタのポインタ」の仕組みを「図」にしてみたいと思います。
「ポインタの変数」も「変数」なので、「メモリアドレス」があります。
その「メモリアドレス」を格納しているのが「ポインタのポインタ」の変数なんですね。
何回か考えてみてやっと理解できた。
この仕組みを使ったプログラムは次のようになります。
#include <stdio.h> int main(void) { // 変数を作る int num = 0; // ポインタ変数を作る int *pNum; // ポインタのポインタ変数を作る int **ppNum; //変数のアドレスを「ポインタ」に格納する pNum = # // ポインタを利用して変数に値を格納する ppNum = &pNum; **ppNum = 2; printf("num=%d\n", num); return 0; }「ポインタのポインタ」を利用すると、プログラムもどんどん複雑になっていきますね。
難しいと感じることも多いですが、理解できるとプログラミング学習にも「楽しさ」を感じることができます。
メモリの確保と解放
「データ型」を指定して「変数」を宣言することでも、メモリにデータの保存領域を作ることができますが、直接「バイト数」を指定してメモリ領域を確保する方法もあります。
その時に使うのが、「malloc」という関数です。
引数にメモリのバイト数を指定すると、戻り値にポインタが返ってくるため、ポインタから確保したメモリ領域にアクセスすることができます。
そして、確保したメモリ領域が不要になった場合は「free」関数を使ってメモリ領域を解放してあげる必要があるとのこと。
「malloc」関数と「free」関数は、「stdlib.h」で定義されているためこのヘッダーファイルを読み込む必要があります。
プログラムを作ってみると、
#include <stdio.h> #include <stdlib.h> int main(void) { // 変数を作る int *num; // int型5個分(20バイト分)のメモリ領域を確保する num = malloc(sizeof(int)*5); if( num != NULL ){ num[0] = 1; num[1] = 2; num[2] = 3; num[3] = 4; num[4] = 5; printf("num[0]=%d\n", num[0]); printf("num[1]=%d\n", num[1]); printf("num[2]=%d\n", num[2]); printf("num[3]=%d\n", num[3]); printf("num[4]=%d\n", num[4]); // 確保したメモリ領域を解放する free(num); } return 0; }
のようになります。
メモリの容量が足りなくなった場合は、「realloc」関数で確保したメモリサイズを拡張することができるそうです。
例えば、int型のサイズを後5個分増やしたいときは、
#include <stdio.h> #include <stdlib.h> int main(void) { // 変数を作る int *num; // int型が5個入るメモリ領域を確保 num = malloc(sizeof(int)*5); num[0] = 1; num[1] = 2; num[2] = 3; num[3] = 4; num[4] = 5; // int型が10個入るメモリ領域を確保 // メモリ領域の確保に失敗すると、retにNULLが入る int *ret = realloc(num,sizeof(int)*10); if( ret == NULL ){ printf("メモリの確保に失敗しました。"); free(num); exit(1); } else { num = ret; } num[5] = 6; num[6] = 7; num[7] = 8; num[8] = 9; num[9] = 10; printf("num[0]=%d\n", num[0]); printf("num[1]=%d\n", num[1]); printf("num[2]=%d\n", num[2]); printf("num[3]=%d\n", num[3]); printf("num[4]=%d\n", num[4]); printf("num[5]=%d\n", num[5]); printf("num[6]=%d\n", num[6]); printf("num[7]=%d\n", num[7]); printf("num[8]=%d\n", num[8]); printf("num[9]=%d\n", num[9]); // メモリ領域を解放 free(ret); free(num); return 0; }
この関数は、新しく指定したサイズのメモリ領域を確保し、第一引数に渡したデータを新しい領域にコピーした後に、古い領域のデータは解放してくれるみたいです。
自分には複雑すぎてついていくのが大変・・・
でも、「脱・プログラミング初心者」を行うためには理解しないといけない「壁」のような気もします。
今後も「ポインタ」についていろいろと勉強して理解を深めていきたいと思います。