【C言語】ポインタをお絵描きで復習

ポインタをお絵描きで復習するページのアイキャッチ

C言語のポインタを理解するためには、ポインタを使ったプログラムをプログラミングしてみることと、ポインタの動きを図を描いて理解することだと思います。

このページでは後者の「ポインタの動きを図を書いて理解する」ことに焦点を当て、ポインタの動きをお絵描きすることでポインタの復習を行っていきたいと思います。プログラミングは不要です。とにかく図を書いてポインタを理解していきます。

下記ページではポインタ初心者の方向けにポインタの解説を行っていますので、まだポインタの理解があまりできていないという方はこちらのページを先に読んでいただければと思います。

ポインタ解説ページのアイキャッチ徹底図解!C言語ポインタを初心者向けに分かりやすく解説

お絵描きでのポインタ復習のやり方

お絵描きでポインタを復習する上で、まず準備していただきたいのが下図のような表です。

ベースとなるメモリ空間

これをメモリ空間として考えます。

1つのセルは1バイトを表しているとし、縦軸の数値はその行の先頭アドレスを示しています。上の図だとアドレス 0x400x7F のメモリ空間を表していることになります(以後、横軸の数値は省略します)。

そして、このメモリ上に変数を配置したり、ポインタがどの変数を指しているかを表す矢印を描画することで、ポインタの動きをお絵描きで表現します。

実際に紙に書いても良いですし、パワポで用意しても良いです。また頭の中でイメージするだけでも良いです。

大事なのはこのメモリ空間を意識することと、図でポインタを考えることです。

例えば下記のようなプログラムを考えてみましょう。

例題
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言語では変数を利用するためにはこの変数宣言が必ず必要です。

さらに、変数宣言が行われた際には、それらの変数がメモリ空間上に配置されます(ポインタも)。

図で考えると下図のように xpx がメモリ空間上に配置されることになります。このようにメモリ空間上に変数が配置(変数用にメモリが確保)されることで、その変数が利用できるようになります。

変数宣言

配置される位置に関しては、まずは特に意識する必要はないです。実際のプログラムでも変数がどのようにメモリ上に配置されるかは分かりませんので。

ただしサイズについては意識した方が良いと思います。

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 に対して矢印が伸びるようなものを描画することができます。

アドレスの格納

続いては値の表示です。

例題(値の表示1)
printf("x = %c\n", x);

 ↑ は変数 x に格納されている値を表示することになります。

値の表示1

例題(値の表示2)
printf("&x = %p\n", &x);

 ↑ は変数 x のアドレスを表示することになります。

値の表示2

例題(値の表示3)
printf("px = %p\n", px);

 ↑ は変数 px に格納されている値を表示、つまり px の矢印が差しているアドレスを表示することになります。

値の表示3

例題(値の表示4)
printf("*px = %c\n", *px);

 ↑ は変数 px が指すアドレスに格納されている値を表示することになります。

値の表示4

これでプログラムを一通り図で表現したことになりますね。

こんな感じで変数とポインタの指す矢印を描きながらポインタを理解するというのがこのページの趣旨です。

ここまでは例題でしたので回答も一緒に解説してきましたが、ここからは問題形式で出題していきます!

答えは隠していますので、ご自身で図を描画し終わった時に答えを開き、認識があっていたかどうかを確認してみてください!

ポインタの基本

まずはポインタの基本をお絵描きで理解していきましょう。

変数宣言

下記プログラム(変数宣言のみ)を実行した時にメモリ空間上にどのように変数が配置されるかを、お絵描きして考えてみましょう。

変数宣言
char x;
int y;
char *px;
int *py;

図で描画すると下のような感じです。

変数宣言

下記の3つさえ意識してお絵描きできていれば正解です!

  • 変数 x のサイズは1バイト
  • 変数 y のサイズは4バイト
  • 変数 pxpy のサイズは4バイト(もしくは 8バイト)

char 型や int 型は型によってサイズが異なりますが、ポインタは char * でも int * でも全て同じサイズになります。

じゃあ char *int * って何が違うの?って思った方は是非ポインタの型も解いてみてください!

アドレスの格納1

下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。

アドレスの格納1
char x;
int y;
char *px;
int *py;

px = &x;
py = &y;

図で描画すると下のような感じです。

アドレスの格納

まだまだ簡単ですね?!

下記さえ意識してお絵描きできていれば正解です!

  • px が変数 x を指している
  • py が変数 y を指している

アドレスの格納2

下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。

アドレスの格納2
char x;
char *pa;
char *pb;

pa = &x;
pb = pa;

図で描画すると、まず pa が下図のように x を指します。

アドレスの格納2−1

そして pb = pa; により、pa に格納されている値が pb に代入されます。

pax を指している、つまり x のアドレスが格納されていますので、pb にも x のアドレスが格納されます。

つまり pbpa と同じく x を指すようになります。

アドレスの格納2−2

下記さえ意識してお絵描きできていれば正解です!

  • pa と pb が同じところを指している
  • その指している先は x である

スポンサーリンク

ポインタと配列

ポインタでは配列も指すことが可能です。次はポインタで配列を指した時のプログラムの動きをお絵描きしていきましょう!

ポインタと配列の変数宣言

下記プログラム(変数宣言のみ)を実行した時にメモリ空間上にどのように変数が配置されるかを、お絵描きして考えてみましょう。

ポインタと配列の変数宣言
char x[4];
char *px;

図で描画すると下のような感じになります。

配列とポインタ

下記さえ意識してお絵描きできていれば正解です!

  • 横方向に1バイトを4つ並べて配列 x を表現
  • px のサイズは4バイト

配列は、メモリ上に「型のサイズ x 配列のサイズ分」のサイズで配置されることになります。ここがポイントです。

配列を指すポインタ1

下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。

配列を指すポインタ1
char x[4];
char *px;

px = x;

図で描画すると下のような感じになります。

配列を指すポインタ

下記さえ意識してお絵描きできていれば正解です!

  • px が配列 x の先頭を指している

配列名は、その配列の先頭アドレスを表します。ですので pxx の先頭を指すことになります。先頭以外を指している場合は間違いですので注意しましょう!

また配列名はアドレスを表しますので、ポインタにアドレスを格納する際に “&” が不要である点もポイントです。

配列を指すポインタ2

下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。

配列を指すポインタ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 に格納され、ppxpx を指すことになります。

ポインタの型

次はここまであまり意識してこなかったポインタの型についても迫っていきます。

ポインタの型とポインタへの加算1

下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。今回は①の時点と②の時点のポインタ cpip が指す場所を考えてみてください。

ポインタの型とポインタへの加算1
char x[8];
char *cp;
int *ip;

cp = x;
ip = x;

/** ① **/

cp = cp + 1;
ip = ip + 1;

/** ② **/

①の時点の各ポインタが指す先をお絵描きすると下のようになります。

ポインタの型1

下記さえ意識してお絵描きできていればここまでは正解です!

  • ip と cp 両方が配列 x を指している

前述の通り配列名は配列の先頭のアドレスを表しますので、 ip = x;cp = x;ipcp に配列 x の先頭を指させる処理となります。

ipint *cpchar * で型が違いますがここまでは同じ動きをします。

ただし型が異なることで、演算時の動きが異なります。

②の時点の各ポインタが指す先をお絵描きすると下のようになります。

ポインタの型2

下記さえ意識してお絵描きできていればここまでは正解です!

  • ip は配列 xの先頭から4バイト分離れたアドレスを指している
  • cp は配列 xの先頭から1バイト分離れたアドレスを指している

ポインタに対して+1すると、ポインタの型のベースの型(int * なら intchar * なら char)のサイズ分、指すアドレスが変化することになります。

つまり、ポインタが int * 型であれば+1すると4バイト、ポインタが char * 型であれば+1すると1バイトアドレスが加算されることになります。

ここがポインタの型によって動きが異なる点となります。

ポインタの型とポインタへの加算2

下記プログラムを実行した時にポインタがどこを指すかを、お絵描きして考えてみましょう。

ポインタの型とポインタへの加算2
int x;
char *px;

px = &x;

px = px + 1;

図で描画すると下のような感じになります。

ポインタの型3

下記さえ意識してお絵描きできていれば正解です!

  • px が変数 x のアドレスのを1バイト分足したアドレスを指している

x 自体は int 型ですが、pxchar * 型なので、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;
}

図で描画すると下のような感じになります。

関数とポインタ1

下記さえ意識してお絵描きできていれば正解です!

  • x と a が、yb がそれぞれ別のメモリ上に配置されている
  • ab にはそれぞれ 'B''A' が格納されているが、xy にはそれぞれ 'A''B' が格納されたまま

関数の引数の変数は、関数呼び出し時に指定する変数とは別のものであり、メモリ上にも全く別ものとして配置されます。

ですので、func 関数の引数である ab の値を変更したとしても、main 関数で使用している xy の値には全く影響がありません。

関数とポインタ引数

下記プログラムを実行した時、①時点で各変数がどのようにメモリ上に配置されるかを、お絵描きして考えてみましょう。ただし今回も 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 が、yb がそれぞれ別のメモリ上に配置されている
  • xy にはそれぞれ 'B''A' が格納されており、関数実行により値が変わっている

関数の引数の変数は、関数呼び出し時に指定する変数とは別のものであり、メモリ上にも全く別ものとして配置されます。が、今回は関数の引数がポインタであり、引数として ab にはそれぞれ xy のアドレスが格納されます。

つまり ab はそれぞれ xy を指していますので、*a を変更すれば x の値を、*b を変更すれば y の値を変更することができます。

こんな感じで、関数の引数をポインタとすることで、関数呼び出し側で宣言された変数に格納されている値を変更することも可能です。

通常関数は1つの戻り値しか返却できませんが、ポインタを引数とすることで複数の値を返却すること(関数呼び出し側に伝えること)も可能です。

ポインタと malloc</h2>

今度はポインタと malloc の動きをお絵描きで復習していきます。

malloc 関数は、引数と指定したサイズ分のメモリを確保し、そのメモリの先頭アドレスを返却する関数になります(メモリの確保とは、そのプログラム内でのみ使用できるメモリを OS から取得することです)。

ポインタと malloc

下記プログラムを実行した時、どのようにメモリ上に配置され、ポインタがどこを指すかをお絵描きして考えてみましょう。

ポインタと <code>malloc</code>
char *px;

px = malloc(5);

図で描画すると下のような感じになります。

ポインタとmalloc

下記さえ意識してお絵描きできていれば正解です!

  • px が malloc 関数で確保された5バイト分のメモリの先頭アドレスを指している

malloc 関数を使用すれば変数宣言を行わなくてもメモリを確保し、そのメモリをプログラム内で使用することが可能になります。さらに  malloc 関数はそのメモリの先頭アドレスを返却しますので、pxmalloc 関数で確保したメモリの先頭を指すことになります。

ポインタと free

malloc がメモリを確保する関数であるのに対し、free 関数は malloc 等で確保したメモリを解放する(OS にもう使わないと宣言する)関数になります。

ポインタと free

下記プログラムを実行した時、どのようにメモリ上に配置され、ポインタがどこを指すかをお絵描きして考えてみましょう。

ポインタと <code>free</code>
char *px;

px = malloc(5);

free(px);

図で描画すると下のような感じになります。

ポインタとfree

下記さえ意識してお絵描きできていれば正解です!

  • px が malloc 関数で確保された5バイト分のメモリの先頭アドレスを “まだ” 指している
  • malloc 関数で確保された5バイト分のメモリが解放されている

free 関数を使用すれば malloc 関数で確保したメモリを解放することができます。メモリを解放することで、そのメモリを他のプログラムで使用することができるようになりますので、こまめに free を行うことで OS 全体のプログラムで使用するメモリを節約することが可能です。

ただし、pxmalloc 関数で確保したメモリをまだ指していますので、*px; などのように px の指す先にアクセスすると不正アクセスになってプログラムが落ちる可能性もありますので注意が必要です。

スポンサーリンク

まとめ

このページではポインタでお絵描きで復習するための問題とその回答例の紹介を行いました。

ポインタがどのように動作するかを考えるには図を書くのが一番効果的です。プログラムがどのように動くかが分かりにくい場合は図を書いて動きを整理してみましょう!

おそらく今回紹介した問題を正解できた方、解説を読んで納得できた方はポインタの理解はバッチリ出来ていると考えて良いです。

それでもまだ苦手意識がある方は、あとはポインタを使って慣れるのみです。

ポインタをどんどん使ってポインタに慣れていきましょう!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です