コラム
2024/10/31
第84回 プログラミングについて『いまさらですがC言語の門を叩こう その7 ~ ポインタ ~』

さあポインタです。C言語を勉強するにあたって一番わかりづらいのがポインタです。と言ってもあまり構えないで下さい。分かってしまえばなーんだこんなものなのかというものですから。
C言語の入門のお話しを始めてからもポインタ(アドレス)の話しはちょっと出てきました。コンピュータ内ではどんなデータにもアドレスがあり、そのデータにアクセスするために内部ではアドレスの計算はごく当たり前の様に行われています。FORTRANなどのポインタのない言語であってもプログラムの実行中にはアドレス計算の嵐なのです。ただFORTRANの場合は言語としてはアドレス計算を表に出していないだけなのです。それに対してC言語ではアドレス計算を言語の仕様に組み入れることで、コンピュータとしては逆に自然な言語になっているのです。
ここではアドレスが4バイト(32ビット)で表現されるものとして話しを進めていきます。
いま次のようなプログラムがあったとします。
char i,j;
i=10;
j=i+20;
このプログラムをコンパイラは次のような機械語に変換します。
(1)変数 i のためのメモリーを1バイト確保する。
(2)変数 j のためのメモリーを1バイト確保する。
(3)値 10 を (1)で確保したアドレスの場所に書き込む。
(3)(1)のアドレスの1バイトに値 20 を加算して (2)で確保したアドレスの場所にその結果を書き込む。
おおよそこのようになります。実際にプログラムの実行時には変数名 i とか j とかいうものは一切ありませんので、もう少し実際のイメージに近く表現すると、
(1)メモリーを1バイト確保する。
(2)メモリーを1バイト確保する。
(3)値 10 を (1)で確保したアドレスの場所に書き込む。
(3)(1)のアドレスの1バイトに値 20 を加算して (2)で確保したアドレスの場所にその結果を書き込む。
となるのです。コンピュータのアーキテクチュアによってはメモリー間での加算などが直接できないものもあるので、一度汎用レジスタに値をロードしてきてからレジスタ内で加算を行なってメモリーに書き込むということもあります(現在はこっちの方が一般的かも知れません)。
このように、コンピュータ内ではアドレス計算が頻繁に行われているのです。先ほどもお話ししましたようにC言語は、こういうコンピュータ内でのアドレス計算をプログラ記述できるようにしたのです。このアドレス計算に使われるのがポインタというものなのです。
ポインタと言っても単なる4バイトの整数の値でしかありません。この4バイトの値をどう扱うかが問題なだけなのです。
C言語でのポインタは次のように定義します。
char *p;
なーんだこれだけ...なのです。要するに変数を宣言するときに変数名の前に * 記号を付けるだけなのです。ここで問題はこのように定義した変数 p のサイズはいくつかということです。char 型だからといって1バイトではなく4バイトになるのです。多分ここも理解しづらい部分だと思いますので、
char i; i は『1バイトの整数値を入れるための箱』。
char *p; p は『1バイトの整数値を入れるための箱』の場所を入れる箱。
と考えていただければなんとなく理解できるのではないでしょうか。ということは、
double *p;
は、『8バイトの実数値を入れるための箱』の場所を入れる箱。という意味になり、場所即ちアドレスを入れるのであるから4バイトのサイズになる訳です。ということで、アドレスが4バイトで表現されるシステムの場合は、どんなサイズの値を入れる箱で場所は4バイトとなる訳です。私達の日常でも同じで、小さな村の郵便番号でも、大きな町の郵便番号でも決まった桁数で表現しています。
それでは、
char **p;
という表現もC言語ではできるのですが、これはいったいどういう意味になるのでしょうか。これは、
『1バイトの値を入れるための箱』の『場所を入れる箱』の場所を入れる箱。
という意味になります。だんだん言葉の遊びみたくなってきましたが私はまじめです。
これを郵便にたとえると、
『はがき』『を投函するポスト』の場所。
ということになります。この変数の定義の場合次のような意味合いになります。
値(1バイト)
↑
└─アドレスの入る箱(4バイト)
↑
└─アドレスの入る箱(4バイト)
(これが char **p;)
なんとなく理解していただけたでしょうか。
char *p;
と定義しても、
char i;
と同じく自動変数のときには変数の初期値は不定です。ポインタ変数だからと言って最初からアドレスが入っている訳ではないのです。ですので、
char *p;
*p=100;
と p を初期値のままにして p の内容の場所に 100 という値を入れようとすると、よっぽど運が良い場合でない限り、プログラムがエラーを発生します。運が良くてもメモリーの予期もしない場所に書き込んで正常に動作しなくなる可能性があります。話しが前後してしまいましたが、ポインタ変数の内容の示しているところに値を入れたり逆に値を取り出すには、上の様に * を付けて記述するのです。
『ポインタ変数を使うにはまずその変数自体に目的の場所をいれておく』
これが基本です。というよりも絶対に守らなければなりません。ですので、
char i,*p;
p= &i;
*p=100;
とすれば i に 100 が入るのです。これもすごく手品を見ている様で雲をつかむような感じだと思います。もっと詳しく説明しましょう。
char i,*p;
では文字型の変数 i (サイズは1バイトの値を入れるので1バイト)と、文字型の値を示すポインタ p (サイズはアドレスを入れるので4バイト)を定義(メモリーに確保)します(下図)。
i:□ p:□□□□
次に、
p= &i;
は前回の演算子のときには説明しませんでしたが、& はアドレス演算子で i のアドレスを p に代入しています。いま i のアドレスが 500 番地であったとすると、p に 500が入ることになります。
i:□ p:□□□□
(500)
そして、
*p=100;
は p の示しているところ即ち p の値の番地の場所に 100 を代入するするということになるので結果として i に 100 が入ることになるのです。
i:□ p:□□□□
↑ (500)
│ │
└──────────┘
100
ポインタの基本はこれだけです。基本と言ってもこれが全てで、後はこれが複雑になるかどうかの違いしかありません。
ポインタは配列も指し示します。
int i[10],*p;
p=i;
*p=100;
と記述すればいいだけなのですが、ここで p=i; と i に & が指定されていないのは、以前にもお話ししたように配列はアドレスなので、そのまま代入できるからです。もしi の5番目の要素(添え字が4)のアドレスを入れるには配列の要素は値ですので、
p= &i[4];
としなければならないことに気を付けて下さい。再び上の例に戻って、*p=100; は配列i の最初の要素に 100 を代入することになりますが、これは実際には、
*(p+0)=100;
と書くこともできます。要するに p に入っているアドレスに何も加えないでそのアドレスの場所ということです。
*(p+1)=100;
と書くとこれは p に入っているアドレスに 1 を加えるのではなく、ポインタ変数 p がint 型を指し示すものなので、コンパイラは配列としては添え字が 1 の要素即ち2番目の要素と解釈し、int 型のサイズが4バイトだったときには p に 4 を加え、その値をアドレスとしてその場所に 100 を代入することになるのです。
C言語とはいい加減なもので、配列として定義した変数であってもポインタの様に、
またはその逆でも記述できます。ですので、
int i[10],*p;
p= &i[4];
は、
int i[10],*p;
p=i+4;
と全く同じで、
int i[10],*p;
p=i;
*(p+4)=100;
と、
int i[10],*p;
p=i;
p[4]=100;
も同じ意味なのです。しかしながら、
int i[10],*p;
i=p;
とはできません。この場合 i は配列のアドレスであって、配列のアドレスを入れるものではないからなのです。
極端な言い方をすると、C言語ではポインタが使えないとプログラムが書けないほど重要なものです。今回は説明しませんでしたが、構造体へのポインタ、関数へのポインタなどいろいろあるのですが、基本は同じですので試してみて下さい。
ではまた次回。
C言語の入門のお話しを始めてからもポインタ(アドレス)の話しはちょっと出てきました。コンピュータ内ではどんなデータにもアドレスがあり、そのデータにアクセスするために内部ではアドレスの計算はごく当たり前の様に行われています。FORTRANなどのポインタのない言語であってもプログラムの実行中にはアドレス計算の嵐なのです。ただFORTRANの場合は言語としてはアドレス計算を表に出していないだけなのです。それに対してC言語ではアドレス計算を言語の仕様に組み入れることで、コンピュータとしては逆に自然な言語になっているのです。
ここではアドレスが4バイト(32ビット)で表現されるものとして話しを進めていきます。
いま次のようなプログラムがあったとします。
char i,j;
i=10;
j=i+20;
このプログラムをコンパイラは次のような機械語に変換します。
(1)変数 i のためのメモリーを1バイト確保する。
(2)変数 j のためのメモリーを1バイト確保する。
(3)値 10 を (1)で確保したアドレスの場所に書き込む。
(3)(1)のアドレスの1バイトに値 20 を加算して (2)で確保したアドレスの場所にその結果を書き込む。
おおよそこのようになります。実際にプログラムの実行時には変数名 i とか j とかいうものは一切ありませんので、もう少し実際のイメージに近く表現すると、
(1)メモリーを1バイト確保する。
(2)メモリーを1バイト確保する。
(3)値 10 を (1)で確保したアドレスの場所に書き込む。
(3)(1)のアドレスの1バイトに値 20 を加算して (2)で確保したアドレスの場所にその結果を書き込む。
となるのです。コンピュータのアーキテクチュアによってはメモリー間での加算などが直接できないものもあるので、一度汎用レジスタに値をロードしてきてからレジスタ内で加算を行なってメモリーに書き込むということもあります(現在はこっちの方が一般的かも知れません)。
このように、コンピュータ内ではアドレス計算が頻繁に行われているのです。先ほどもお話ししましたようにC言語は、こういうコンピュータ内でのアドレス計算をプログラ記述できるようにしたのです。このアドレス計算に使われるのがポインタというものなのです。
ポインタと言っても単なる4バイトの整数の値でしかありません。この4バイトの値をどう扱うかが問題なだけなのです。
C言語でのポインタは次のように定義します。
char *p;
なーんだこれだけ...なのです。要するに変数を宣言するときに変数名の前に * 記号を付けるだけなのです。ここで問題はこのように定義した変数 p のサイズはいくつかということです。char 型だからといって1バイトではなく4バイトになるのです。多分ここも理解しづらい部分だと思いますので、
char i; i は『1バイトの整数値を入れるための箱』。
char *p; p は『1バイトの整数値を入れるための箱』の場所を入れる箱。
と考えていただければなんとなく理解できるのではないでしょうか。ということは、
double *p;
は、『8バイトの実数値を入れるための箱』の場所を入れる箱。という意味になり、場所即ちアドレスを入れるのであるから4バイトのサイズになる訳です。ということで、アドレスが4バイトで表現されるシステムの場合は、どんなサイズの値を入れる箱で場所は4バイトとなる訳です。私達の日常でも同じで、小さな村の郵便番号でも、大きな町の郵便番号でも決まった桁数で表現しています。
それでは、
char **p;
という表現もC言語ではできるのですが、これはいったいどういう意味になるのでしょうか。これは、
『1バイトの値を入れるための箱』の『場所を入れる箱』の場所を入れる箱。
という意味になります。だんだん言葉の遊びみたくなってきましたが私はまじめです。
これを郵便にたとえると、
『はがき』『を投函するポスト』の場所。
ということになります。この変数の定義の場合次のような意味合いになります。
値(1バイト)
↑
└─アドレスの入る箱(4バイト)
↑
└─アドレスの入る箱(4バイト)
(これが char **p;)
なんとなく理解していただけたでしょうか。
char *p;
と定義しても、
char i;
と同じく自動変数のときには変数の初期値は不定です。ポインタ変数だからと言って最初からアドレスが入っている訳ではないのです。ですので、
char *p;
*p=100;
と p を初期値のままにして p の内容の場所に 100 という値を入れようとすると、よっぽど運が良い場合でない限り、プログラムがエラーを発生します。運が良くてもメモリーの予期もしない場所に書き込んで正常に動作しなくなる可能性があります。話しが前後してしまいましたが、ポインタ変数の内容の示しているところに値を入れたり逆に値を取り出すには、上の様に * を付けて記述するのです。
『ポインタ変数を使うにはまずその変数自体に目的の場所をいれておく』
これが基本です。というよりも絶対に守らなければなりません。ですので、
char i,*p;
p= &i;
*p=100;
とすれば i に 100 が入るのです。これもすごく手品を見ている様で雲をつかむような感じだと思います。もっと詳しく説明しましょう。
char i,*p;
では文字型の変数 i (サイズは1バイトの値を入れるので1バイト)と、文字型の値を示すポインタ p (サイズはアドレスを入れるので4バイト)を定義(メモリーに確保)します(下図)。
i:□ p:□□□□
次に、
p= &i;
は前回の演算子のときには説明しませんでしたが、& はアドレス演算子で i のアドレスを p に代入しています。いま i のアドレスが 500 番地であったとすると、p に 500が入ることになります。
i:□ p:□□□□
(500)
そして、
*p=100;
は p の示しているところ即ち p の値の番地の場所に 100 を代入するするということになるので結果として i に 100 が入ることになるのです。
i:□ p:□□□□
↑ (500)
│ │
└──────────┘
100
ポインタの基本はこれだけです。基本と言ってもこれが全てで、後はこれが複雑になるかどうかの違いしかありません。
ポインタは配列も指し示します。
int i[10],*p;
p=i;
*p=100;
と記述すればいいだけなのですが、ここで p=i; と i に & が指定されていないのは、以前にもお話ししたように配列はアドレスなので、そのまま代入できるからです。もしi の5番目の要素(添え字が4)のアドレスを入れるには配列の要素は値ですので、
p= &i[4];
としなければならないことに気を付けて下さい。再び上の例に戻って、*p=100; は配列i の最初の要素に 100 を代入することになりますが、これは実際には、
*(p+0)=100;
と書くこともできます。要するに p に入っているアドレスに何も加えないでそのアドレスの場所ということです。
*(p+1)=100;
と書くとこれは p に入っているアドレスに 1 を加えるのではなく、ポインタ変数 p がint 型を指し示すものなので、コンパイラは配列としては添え字が 1 の要素即ち2番目の要素と解釈し、int 型のサイズが4バイトだったときには p に 4 を加え、その値をアドレスとしてその場所に 100 を代入することになるのです。
C言語とはいい加減なもので、配列として定義した変数であってもポインタの様に、
またはその逆でも記述できます。ですので、
int i[10],*p;
p= &i[4];
は、
int i[10],*p;
p=i+4;
と全く同じで、
int i[10],*p;
p=i;
*(p+4)=100;
と、
int i[10],*p;
p=i;
p[4]=100;
も同じ意味なのです。しかしながら、
int i[10],*p;
i=p;
とはできません。この場合 i は配列のアドレスであって、配列のアドレスを入れるものではないからなのです。
極端な言い方をすると、C言語ではポインタが使えないとプログラムが書けないほど重要なものです。今回は説明しませんでしたが、構造体へのポインタ、関数へのポインタなどいろいろあるのですが、基本は同じですので試してみて下さい。
ではまた次回。