C言語のポインタを理解するためには、ポインタを使ったプログラムをプログラミングしてみることと、ポインタの動きを図を描いて理解することだと思います。
このページでは後者の「ポインタの動きを図を書いて理解する」ことに焦点を当て、ポインタの動きをお絵描きすることでポインタの復習を行っていきたいと思います。プログラミングは不要です。とにかく図を書いてポインタを理解していきます。
下記ページではポインタ初心者の方向けにポインタの解説を行っていますので、まだポインタの理解があまりできていないという方はこちらのページを先に読んでいただければと思います。
【C言語】ポインタを初心者向けに分かりやすく解説Contents
お絵描きでのポインタ復習のやり方
お絵描きでポインタを復習する上で、まず準備していただきたいのが下図のような表です。
これをメモリ空間として考えます。
1つのセルは1バイトを表しているとし、縦軸の数値はその行の先頭アドレスを示しています。上の図だとアドレス 0x40
〜 0x7F
のメモリ空間を表していることになります(以後、横軸の数値は省略します)。
そして、このメモリ上に変数を配置したり、ポインタがどの変数を指しているかを表す矢印を描画することで、ポインタの動きをお絵描きで表現します。
実際に紙に書いても良いですし、パワポで用意しても良いです。また頭の中でイメージするだけでも良いです。
大事なのはこのメモリ空間を意識することと、図でポインタを考えることです。
例えば下記のようなプログラムを考えてみましょう。
char x;
char *px;
x = 'A';
px = &x;
printf("x = %c\n", x);
printf("&x = %p\n", &x);
printf("px = %p\n", px);
printf("*px = %c\n", *px);
まずは下記部分について考えてみます。
char x;
char *px;
単なる変数宣言ですね。C言語では変数を利用するためにはこの変数宣言が必ず必要です。
さらに、変数宣言が行われた際には、それらの変数がメモリ空間上に配置されます(ポインタも)。
図で考えると下図のように x
と px
がメモリ空間上に配置されることになります。このようにメモリ空間上に変数が配置(変数用にメモリが確保)されることで、その変数が利用できるようになります。
配置される位置に関しては、まずは特に意識する必要はないです。実際のプログラムでも変数がどのようにメモリ上に配置されるかは分かりませんので。
ただしサイズについては意識した方が良いと思います。
char
型で変数宣言した変数はサイズが1バイトになります。ですので上の図では変数 x
は1セル分のみで表しています。ちなみに int
型の変数はサイズが4バイトになります(4セル分で表します)。
ポインタ型(int*
、char*
など)のサイズは CPU によって異なります。32ビット CPU の場合は4バイト、64ビット CPU の場合は8バイトになります。このページでは4バイト(つまり4セル分で表現)することとしてお絵描きしていきたいと思います。
また、ポインタ変数の中身にはポインタであることが分かるように “●” 印をつけています。
プログラムに戻ります。続いて実行されるのは下記になります。
x = 'A';
x
に 'A'
が代入されますので、図としては下のようになります。
さらに次は下記が実行されます。
px = &x;
これは px
に変数 x
のアドレスを格納する処理になります(図だと 0x6A
が格納される)。つまり、これにより px
が変数 x
を指すことになります。ですので、図としては下のように px
から x
に対して矢印が伸びるようなものを描画することができます。
続いては値の表示です。
printf("x = %c\n", x);
↑ は変数 x に格納されている値を表示することになります。
printf("&x = %p\n", &x);
↑ は変数 x のアドレスを表示することになります。
printf("px = %p\n", px);
↑ は変数 px に格納されている値を表示、つまり px の矢印が差しているアドレスを表示することになります。
printf("*px = %c\n", *px);
↑ は変数 px が指すアドレスに格納されている値を表示することになります。
これでプログラムを一通り図で表現したことになりますね。
こんな感じで変数とポインタの指す矢印を描きながらポインタを理解するというのがこのページの趣旨です。
ここまでは例題でしたので回答も一緒に解説してきましたが、ここからは問題形式で出題していきます!
答えは隠していますので、ご自身で図を描画し終わった時に答えを開き、認識があっていたかどうかを確認してみてください!
ポインタの基本
まずはポインタの基本をお絵描きで理解していきましょう。
スポンサーリンク
変数宣言
下記プログラム(変数宣言のみ)を実行した時にメモリ空間上にどのように変数が配置されるかを、お絵描きして考えてみましょう。
char x;
int y;
char *px;
int *py;
図で描画すると下のような感じです。
下記の3つさえ意識してお絵描きできていれば正解です!
- 変数
x
のサイズは1バイト - 変数
y
のサイズは4バイト - 変数
px
とpy
のサイズは4バイト(もしくは 8バイト)
char
型や int
型は型によってサイズが異なりますが、ポインタは char *
でも int *
でも全て同じサイズになります。
じゃあ char *
と int *
って何が違うの?って思った方は是非ポインタの型も解いてみてください!
アドレスの格納1
下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。
char x;
int y;
char *px;
int *py;
px = &x;
py = &y;
図で描画すると下のような感じです。
まだまだ簡単ですね?!
下記さえ意識してお絵描きできていれば正解です!
px
が変数x
を指しているpy
が変数y
を指している
アドレスの格納2
下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。
char x;
char *pa;
char *pb;
pa = &x;
pb = pa;
図で描画すると、まず pa
が下図のように x
を指します。
そして pb = pa;
により、pa
に格納されている値が pb
に代入されます。
pa
は x
を指している、つまり x
のアドレスが格納されていますので、pb
にも x
のアドレスが格納されます。
つまり pb
も pa
と同じく x
を指すようになります。
下記さえ意識してお絵描きできていれば正解です!
pa
とpb
が同じところを指している- その指している先は
x
である
スポンサーリンク
ポインタと配列
ポインタでは配列も指すことが可能です。次はポインタで配列を指した時のプログラムの動きをお絵描きしていきましょう!
ポインタと配列の変数宣言
下記プログラム(変数宣言のみ)を実行した時にメモリ空間上にどのように変数が配置されるかを、お絵描きして考えてみましょう。
char x[4];
char *px;
図で描画すると下のような感じになります。
下記さえ意識してお絵描きできていれば正解です!
- 横方向に1バイトを4つ並べて配列
x
を表現 px
のサイズは4バイト
配列は、メモリ上に「型のサイズ x 配列のサイズ分」のサイズで配置されることになります。ここがポイントです。
配列を指すポインタ1
下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。
char x[4];
char *px;
px = x;
図で描画すると下のような感じになります。
下記さえ意識してお絵描きできていれば正解です!
px
が配列x
の先頭を指している
配列名は、その配列の先頭アドレスを表します。ですので px
は x
の先頭を指すことになります。先頭以外を指している場合は間違いですので注意しましょう!
また配列名はアドレスを表しますので、ポインタにアドレスを格納する際に “&
” が不要である点もポイントです。
スポンサーリンク
配列を指すポインタ2
下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。
char x[4];
char *px;
px = &(x[2]);
で描画すると下のような感じになります。
下記さえ意識してお絵描きできていれば正解です!
px
が配列x
の第2要素(x[2]
)を指している
こんな感じで &配列名[添字]
と記述することで配列の途中の要素のアドレスを取得することも可能です。
ポインタのポインタ
次はポインタのポインタです。
ポインタのポインタって聞くと難しそうに聞こえますが、結局は指す先がポインタであるだけです。
ポインタも通常通り “&
” によりそのポインタが配置されているアドレスが取得でき、ポインタのポインタには、そのポインタが配置されているアドレスを格納します。
char **ppx;
char *px;
ppx = &px;
つまりポインタを指すポインタ、これがポインタのポインタです。
ポインタのポインタ
下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。
char x;
char *px;
char **ppx;
px = &x;
ppx = &px;
図で描画すると下のような感じになります。
下記さえ意識してお絵描きできていれば正解です!
px
が変数x
を指しているppx
が変数px
を指している
ポインタを指すポインタは、ポインタのポインタ(**変数名
)として変数宣言します。
「ポインタのポインタ」はポインタのアドレスを格納することが可能です。ポインタのアドレスは通常の変数同様に &ポインタ変数
により取得することができます。
つまり ppx = &px;
を行うと、ポインタ px
のアドレスが ppx
に格納され、ppx
が px
を指すことになります。
スポンサーリンク
ポインタの型
次はここまであまり意識してこなかったポインタの型についても迫っていきます。
ポインタの型とポインタへの加算1
下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。今回は①の時点と②の時点のポインタ cp
と ip
が指す場所を考えてみてください。
char x[8];
char *cp;
int *ip;
cp = x;
ip = x;
/** ① **/
cp = cp + 1;
ip = ip + 1;
/** ② **/
①の時点の各ポインタが指す先をお絵描きすると下のようになります。
下記さえ意識してお絵描きできていればここまでは正解です!
ip
とcp
両方が配列x
を指している
前述の通り配列名は配列の先頭のアドレスを表しますので、 ip = x;
も cp = x;
も ip
と cp
に配列 x
の先頭を指させる処理となります。
ip
は int *
、cp
は char *
で型が違いますがここまでは同じ動きをします。
ただし型が異なることで、演算時の動きが異なります。
②の時点の各ポインタが指す先をお絵描きすると下のようになります。
下記さえ意識してお絵描きできていればここまでは正解です!
ip
は配列x
の先頭から4バイト分離れたアドレスを指しているcp
は配列x
の先頭から1バイト分離れたアドレスを指している
ポインタに対して+1すると、ポインタの型のベースの型(int *
なら int
、char *
なら char
)のサイズ分、指すアドレスが変化することになります。
つまり、ポインタが int *
型であれば+1すると4バイト、ポインタが char *
型であれば+1すると1バイトアドレスが加算されることになります。
ここがポインタの型によって動きが異なる点となります。
ポインタの型とポインタへの加算2
下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。
int x;
char *px;
px = &x;
px = px + 1;
図で描画すると下のような感じになります。
下記さえ意識してお絵描きできていれば正解です!
px
が変数x
のアドレスのを1バイト分足したアドレスを指している
x
自体は int
型ですが、px
が char *
型なので、x
のアドレスを格納した状態で+1すると x
のアドレスから1バイト分だけ加算されたアドレスを指すようになります。
こんな感じで int
型のデータを1バイト分ずつ取得するようなことも可能です。
スポンサーリンク
ポインタと関数
次はポインタと関数についてです。関数の引数に普通の変数とポインタ変数を指定した場合とで、どのように動きが異なるかを意識して考えていきましょう。
関数と引数
下記プログラムを実行した時、①時点で各変数がどのようにメモリ上に配置されるかを、お絵描きして考えてみましょう。ただし tmp
変数に関しては図示しなくて良いです。その代わり変数に格納される値(文字)がどのようになるかについても考えてみましょう。
void func(char a, char b){
char tmp;
tmp = b;
b = a;
a = tmp;
/*** ① ***/
}
int main(void){
char x = 'A';
char y = 'B';
func(x, y);
return 0;
}
図で描画すると下のような感じになります。
下記さえ意識してお絵描きできていれば正解です!
x
とa
が、y
とb
がそれぞれ別のメモリ上に配置されているa
とb
にはそれぞれ'B'
と'A'
が格納されているが、x
とy
にはそれぞれ'A'
と'B'
が格納されたまま
関数の引数の変数は、関数呼び出し時に指定する変数とは別のものであり、メモリ上にも全く別ものとして配置されます。
ですので、func
関数の引数である a
と b
の値を変更したとしても、main
関数で使用している x
と y
の値には全く影響がありません。
関数とポインタ引数
下記プログラムを実行した時、①時点で各変数がどのようにメモリ上に配置されるかを、お絵描きして考えてみましょう。ただし今回も tmp
変数に関しては図示しなくて良いです。その代わり変数に格納される値(文字)がどのようになるかについても考えてみましょう。
void func(char *a, char *b){
char tmp;
tmp = *b;
*b = *a;
*a = tmp;
/*** ① ***/
}
int main(void){
char x = 'A';
char y = 'B';
func(&x, &y);
return 0;
}
図で描画すると下のような感じになります。
下記さえ意識してお絵描きできていれば正解です!
x
とa
が、y
とb
がそれぞれ別のメモリ上に配置されているx
とy
にはそれぞれ'B'
と'A'
が格納されており、関数実行により値が変わっている
関数の引数の変数は、関数呼び出し時に指定する変数とは別のものであり、メモリ上にも全く別ものとして配置されます。が、今回は関数の引数がポインタであり、引数として a
と b
にはそれぞれ x
と y
のアドレスが格納されます。
つまり a
と b
はそれぞれ x
と y
を指していますので、*a
を変更すれば x
の値を、*b
を変更すれば y
の値を変更することができます。
こんな感じで、関数の引数をポインタとすることで、関数呼び出し側で宣言された変数に格納されている値を変更することも可能です。
通常関数は1つの戻り値しか返却できませんが、ポインタを引数とすることで複数の値を返却すること(関数呼び出し側に伝えること)も可能です。
スポンサーリンク
ポインタと malloc<
/h2>
今度はポインタと malloc
の動きをお絵描きで復習していきます。
malloc
関数は、引数と指定したサイズ分のメモリを確保し、そのメモリの先頭アドレスを返却する関数になります(メモリの確保とは、そのプログラム内でのみ使用できるメモリを OS から取得することです)。
ポインタと malloc
下記プログラムを実行した時、どのようにメモリ上に配置され、ポインタがどこを指すかをお絵描きして考えてみましょう。
char *px;
px = malloc(5);
図で描画すると下のような感じになります。
下記さえ意識してお絵描きできていれば正解です!
px
がmalloc
関数で確保された5バイト分のメモリの先頭アドレスを指している
malloc
関数を使用すれば変数宣言を行わなくてもメモリを確保し、そのメモリをプログラム内で使用することが可能になります。さらに malloc
関数はそのメモリの先頭アドレスを返却しますので、px
は malloc
関数で確保したメモリの先頭を指すことになります。
ポインタと free
mallo
c がメモリを確保する関数であるのに対し、free
関数は malloc
等で確保したメモリを解放する(OS にもう使わないと宣言する)関数になります。
スポンサーリンク
ポインタと free
下記プログラムを実行した時、どのようにメモリ上に配置され、ポインタがどこを指すかをお絵描きして考えてみましょう。
char *px;
px = malloc(5);
free(px);
図で描画すると下のような感じになります。
下記さえ意識してお絵描きできていれば正解です!
px
がmalloc
関数で確保された5バイト分のメモリの先頭アドレスを “まだ” 指しているmalloc
関数で確保された5バイト分のメモリが解放されている
free
関数を使用すれば malloc
関数で確保したメモリを解放することができます。メモリを解放することで、そのメモリを他のプログラムで使用することができるようになりますので、こまめに free
を行うことで OS 全体のプログラムで使用するメモリを節約することが可能です。
ただし、px
は malloc
関数で確保したメモリをまだ指していますので、*px;
などのように px
の指す先にアクセスすると不正アクセスになってプログラムが落ちる可能性もありますので注意が必要です。
まとめ
このページではポインタでお絵描きで復習するための問題とその回答例の紹介を行いました。
ポインタがどのように動作するかを考えるには図を書くのが一番効果的です。プログラムがどのように動くかが分かりにくい場合は図を書いて動きを整理してみましょう!
おそらく今回紹介した問題を正解できた方、解説を読んで納得できた方はポインタの理解はバッチリ出来ていると考えて良いです。
それでもまだ苦手意識がある方は、あとはポインタを使って慣れるのみです。
ポインタをどんどん使ってポインタに慣れていきましょう!
オススメの参考書(PR)
C言語一通り勉強したけど「ポインタがよく分からない」「ポインタの理解があやふや」「もっとC言語の理解を深めたい」という方には、下記の「C言語ポインタ完全制覇」がオススメです!
この本の主な内容は下記の通りで、通常の参考書では50ページくらいで解説するポインタを、この本では約 "360ページ" 使って幅広く・深く解説しています。
- C言語でのメモリの使い方
- 配列とポインタの関係性
- ポインタのよくある使い方
- ポインタの効果的な使い方
一通りC言語を学んだだけだと "理解があやふやになってしまいがち" "疑問に思いがち" な内容に対する明確な解説が多いため、特にポインタやC言語の理解があやふやという方にはオススメの本です。
また、C言語においてポインタはまさに "肝" となる機能ですので、ポインタについてより深く学ぶことでC言語全体の理解を深めることにもつながります。
ポインタ・C言語についてより深く理解するための本としては現状1番のオススメの本です。
ただし、他の入門書等で "一通りC言語を学んでいる" 方向けの解説になっているので、"C言語を始めるにあたっての最初の入門書" として利用すると難易度が高いので注意してください。
入門用のオススメ参考書は下記ページで紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/