
C言語の「ポインタ」についてお話していきたいと思います。ポインタのお話をする前提となる知識として、C言語からどのようにメモリを使っているかの基礎知識を前ページでまとめていますのでよければそちらもご覧ください。
ポインタを使った一つの例を確認してみます。 例えばRPGなどでキャラクタがダメージを追うとき、体力を表す hitpoint の変数を減らす専用の関数を用意したいとします。 下のようにするとどうでしょうか。
#include <stdio.h>
void damage20(int hp) {
hp = hp - 20; // 20 ダメージを与えたい!
}
int main() {
int hitpoint = 100;
damage20(hitpoint);
printf("hitpoint は %d\n", hitpoint);
}
コードを実行すると、下のような結果になりました。
hitpoint は 100
7行目で damage20 関数を呼び出しており、変数 hitpoint を関数に渡しています。 そして、 3 行目の damange20 関数の中で、変数 hp を 20 引き算しています。 その結果を 8 行目で printf で出力しています。ですが、ちゃんと欲しい結果である 80 になっていません。なぜこのような結果になるのでしょうか。
関数に変数の値を渡すとき、関数の呼び出し側でセットした変数と、呼び出し先の関数の引数に置いた変数は別のものとなります。つまり、3行目の呼び出し元の hitpoint と、呼び出される側の変数 hp は別の変数であり、別のメモリアドレスを参照しています。
実際に関数を呼び出すとき、新しく変数 hp が作成され、hp に呼び出し元の hitpoint の値がコピーされます。(代入されます)
そのため関数の呼び出し先で hp の値を変更したとしても、呼び出し元の hitpoint の値はかわりません。
このような関数の呼び出し方を値渡しと呼びます。
呼び出した関数の先で、変数の中身を変更する例として、下のように書くことができます。
#include <stdio.h>
void damage15(int *hp) {
*hp = *hp - 15; // 15 ダメージを与える
}
int main() {
int hitpoint = 100;
damage15(&hitpoint);
printf("hitpoint は %d\n", hitpoint);
}
コードを実行すると、下のような結果になりました。
hitpoint は 85
ちゃんと 15 ダメージ分が反映され、 hitpoin 変数の中身が想定通り減っています。
今回はdamage15関数の引数が
int *hp
となっています。int型の後に「*」アスタリスクがついています。これがつくことで、これはポインタを入れる変数であることを示します。変数 hp の中にはメモリアドレスを入れることができます。
その後、「*hp」と記載するとそれは変数 hp に設定されたメモリアドレスの中身を参照していることとなります。ですので、
*hp = *hp – 15;
と書くことで、変数 hp のメモリアドレスの中身を 15 引くという演算を行うことができます。
また呼び出し元も前回とは変わっています。
7行目では、damage15(&hitpoint); と記載しており、 変数 hitpoint の頭に「&」を付けています。前回のページでもお話しましたが、これは変数 hitpoint が格納されているメモリアドレスを意味し、アドレス演算子と呼ばれます。
たとえば hitpoint の値がメモリアドレスの 500 番に格納されているとき、&hitpoint の値は 500 となります。
この仕組みを使って、呼び出し側で変数のメモリアドレスを設定し、受取側でポインタ変数でメモリアドレスを受け取るようにすることができます。これにより呼び出し先の関数で、メモリアドレスの先を変更するなど演算が可能となります。
このような関数の呼び出し方、引数の渡し方をアドレス渡しと呼びます。 数値を渡す(呼び出し先の関数にコピーしている)ではなく、メモリのアドレスを渡す(呼び出し先の関数にコピーしている)ことをしているのでアドレス渡しという呼ばれ方をしています。
ポインタ変数には int 型だけでなく、char型や long 型なども指定可能です。書き方は同じように、
int* hitpoint;
char* level;
long* exp;
のようにしてポインタ型変数を書くことができます。 これらのポインタ変数にはいずれもメモリアドレスが代入されます。 int型のサイズや char型のサイズは関係なく、どれも同じようにメモリアドレスが入るため、例えば 4 バイトなどのサイズとなります。 例えば下のようにすることで、メモリサイズを確認できます。
int* hitpoint;
char* level;
long* exp;
printf("int* は %dバイト\n", sizeof(*hitpoint));
printf("char* は %dバイト\n", sizeof(*level));
printf("long* は %dバイト\n", sizeof(*exp));
私の環境ではすべて 4 バイトとなりました。 OS が このプロセスに割り当てた仮想メモリを表現するために、4バイトが使われていることになります。 4バイトは負の数がなければ、2の32乗で4,294,967,296、約4GB ちょいも範囲を持てることになります。
int* は 4バイト
char* は 4バイト
long* は 4バイト
ポインタ変数を宣言するときに int* や char* など、いずれもメモリアドレスが指定されていますが、これは参照するメモリアドレス先から読み取るメモリサイズを特定するために使われます。
例えば、short型は 2 バイトのメモリ領域を使ってデータを保存しています。(筆者環境)
ですので、short* で参照するメモリアドレスの先から 2 バイトぶんを読み取った数がメモリアドレス上に格納されている実際の数値ということになります。
ポインタ変数について最初の導入部分をお話しました。 メモリアドレスという目に見えにくいものが登場するので、ポインタは少し理解しずらいかもしれませんが、使えるようになるとプログラムでできることがぐっと広がるかと思います。
アドレス渡しと参照渡し
C言語にそのものの機能はないですが、アドレス渡しに似たような仕組みとして「参照渡し」があります。
アドレス渡しは、そのまんまアドレスを数値として関数に渡しています。
C++,Java,Python などのプログラミング言語では、この参照渡しを使って、アドレス渡しと同じような処理が実現できます。
参照渡しは、「参照」を渡します。変数などのオブジェクトとよばれるモノに対してのリンクであり、
アドレスの書いてある紙のようなものです。この紙をコピーして関数に渡すと、
渡された関数の中で、参照に書かれている先のオブジェクトにアクセスすることができます。
更新日 | 更新内容 |
---|---|
更新なし |
コメント、ありがとうございます。
ごめんなさい。エラーでうまく送信できませんでした。ご迷惑をおかけします。しばらくおいてから再度送信を試していただくか、以下から DM などでご連絡頂ければと思います。
Twitter:@NodachiSoft_jpお名前:以下の内容でコメントを送信します。よろしければ、「送信」を押してください。修正する場合は「戻る」を押してください
お名前: