8. ポインタ−変数 −その1− |
よく,「ポインターは難しい!」などというひとがいますが,まったく,そんなことはありません.先入観を棄て理解を試みてください.
|
8.1 まずは,メモリーの話から |
ポインター変数を理解するための第1段階は,メモリー中で変数がどのように配置されるかを理解する事です.
まずは,下記のことがらを再度,確認しておきましょう.
1)コンピュータの主要部分は,メモ用紙の役割をするメモリー(主記憶)とそこに書かれた(ロードされた)プログラムを
解読.実行するCPUとで構成される.
2)主記憶はバイトを単位に(とする)区切られていて,このバイト単位のスペースには番号がつけられている.
その番号をアドレス(番地)と呼ぶ.
3)CPUは,複数の命令で構成されるプログラムを上から順に読み込んで解読・実行する.
以上のことを予備知識として,次の図を眺めてみてください.
この図は,整数型の2数の和を求めるプログラムで,実行過程でメモリー中に変数のためのスペースが確保されていく様子を示します.
※ここでは,一つの命令(文),たとえば,a=25を1バイトのスペースに納めていますが,この表現は正確ではありません.実際には,1個の命令(文)で複数バイトのスペースを使用します.
1)int型の場合(4バイト)
addrss | 0)プログラム |
468000 | |
468001 | a = 25 |
468002 | b = 10 |
468003 | c = a+b |
468004 | |
468005 | |
468006 | |
468007 | |
468008 | |
468009 | |
468010 | |
468011 | |
468012 | |
468013 | |
468014 | |
468015 | |
468016 | |
468017 | |
| 1)1行目の実行 |
| 2)2行目の実行 |
| 3)3行目の実行 |
468000 | |
468000 | |
468000 | |
468001 | a = 25 |
468001 | a = 25 |
468001 | a = 25 |
468002 | b = 10 |
468002 | b = 10 |
468002 | b = 10 |
468003 | c = a+b |
468003 | c = a+b |
468003 | c = a+b |
468004 | |
468004 | |
468004 | |
468005 | |
468005 | |
468005 | |
468006
| a: 25 (4バイト) |
468006
| a: 25 (4バイト) |
468006
| a: 25 (4バイト) |
468010
| |
468010
| b: 10 (4バイト) |
468010
| b: 10 (4バイト) |
468014
| |
468014
| |
468014
| c: 35 (4バイト) |
468018 | |
468018 | |
468018 | |
上の図を簡単に説明しましょう.
左端はプログラムが記憶された(ロードされた)状態を示します.
CPUはそのプログラムを先頭から1個ずつ読み込み,解読・実行します.
このプログラムの場合,CPUは次のような処理をします.
実行1: a=25
主記憶のデータ領域の4バイトを変数aに割り当て,そこに数値25を書き込む.
(整数値1個を記憶させるには4バイト必要)
実行2: b=10
主記憶のデータ領域の4バイトを変数bに割り当て,そこに数値10を書き込む.
実行3: c = a+b
主記憶のデータ領域の4バイトを変数cに割り当て,aとbの記憶内容を読み込み,
それらの和を計算し結果をcの領域に書き込む.
2)double型の場合(8バイト)
変数型がdouble型の場合,先の整数型の場合と異なる点は,変数に割り当てられるメモリーのサイズです.
これまで説明したように,double型の数値を納めるには8バイトのサイズのスペースが必要となります,
addrss | 0)プログラム |
468000 | |
468001 | a = 25.4 |
468002 | b = 12.5 |
468003 | c = a+b |
468004 | |
468005 | |
468006 | |
468007 | |
468008 | |
468009 | |
468010 | |
468011 | |
468012 | |
468013 | |
468014 | |
468015 | |
468016 | |
468017 | |
468018 | |
468019 | |
468020 | |
| 1)1行目の実行 |
| 2)2行目の実行 |
| 3)3行目の実行 |
468000 | |
468000 | |
468000 | |
468001 | a = 25.4 |
468001 | a = 25.4 |
468001 | a = 25.4 |
468002 | b = 12.5 |
468002 | b = 12.5 |
468002 | b = 12.5 |
468003 | c = a+b |
468003 | c = a+b |
468003 | c = a+b |
468004 | |
468004 | |
468004 | |
468005 | |
468005 | |
468005 | |
468006
| a: 25.4
(8バイト) |
468006
| a: 25.4
(8バイト) |
468006
| a: 25.4
(8バイト) |
468014
| |
468014
| b: 12.5
(8バイト) |
468014
| b: 12.5
(8バイト) |
468022
| |
468022
| |
468022
| c: 37.9
(8バイト) |
468030 | |
468030 | |
468030 | |
|
8.2 変数のアドレスを調べる |
変数を主記憶のどの番地に確保するかは,私たちが指定することはできません.(と考えておいてください.コンピュータによっては出来る場合もあります.)それは,メモリーを管理する基本ソフトウェアが決めます.
でも,主記憶中のどこに変数が作られたかは知ることができます.
C言語には,変数のアドレスを調べるための演算子して,&(アンパーサント)が用意されています.たとえば,varという変数のアドレスを求めるためには次のように書きます.
&var
演算結果は,アドレスの値が返されます.演算子&は変数名に作用して,そのアドレス値を返す演算子だということです.
次のプログラムを参考に,変数のアドレスの求め方を理解してください.プログラ例8.2.1が整数型変数の場合で,8.2.2がdouble型変数の場合です.
※なお,得られるアドレスの値は,コンピュータ中でのメモリーの使用状況によって異なります.
したがって,同一のプログラムを実行したとしても同じアドレスの値が得られるとは限りません.
プログラム例 8.2.1 | |
実行結果 |
#include <stdio.h>
void main(void){
int a, b, c;
a = 25;
b = 10;
c = a + b;
/*a,b,cが作成されたことを確認*/
printf("1)変数a,b,cが作成されたことを確認\n");
printf(" a=%d\n b=%d\n c=%d\n\n", a, b, c);
/*a,b,cが主記憶の何番地に作られたかを確認*/
printf("2)変数a,b,cの番地\n");
printf(" a:%u\n b:%u\n c:%u\n", &a, &b, &c);
}
|
|
1)変数a,b,cが作成されたことを確認
a=25
b=10
c=35
2)変数a,b,cの番地
a:1245052
b:1245048
c:1245044
|
プログラム例 8.2.2 | |
実行結果 |
#include <stdio.h>
void main(void){
double a, b, c;
a = 14.5;
b = 2.5;
c = a + b;
/*a,b,cに数値が記憶されていることを確認*/
printf("1)変数a,b,cが作成されたことを確認\n");
printf(" a=%f\n b=%f\n c=%f\n\n", a, b, c);
/*a,b,cが主記憶の何番地に作られたかを確認*/
printf("2)変数a,b,cの番地\n");
printf(" a:%u\n b:%u\n c:%u\n", &a, &b, &c);
|
|
1)変数a,b,cが作成されたことを確認
a=14.500000
b=2.500000
c=17.000000
2)変数a,b,cの番地の確認
a:1245048
b:1245040
c:1245032
|
|
8.3 ポインター変数 |
&演算子を用いて得られたアドレス値は,もちろん変数に代入して扱うことが出来ます.アドレスを記憶することのできる変数は,ポインター変数あるいはポインターと呼ばれます.
初心者は,なるべく「ポインター変数」と覚え,ポインターが1種の変数であるということを意識しましょう!
int型,float型,double型などの変数と同様,ポインター変数もプログラムの最初の方で型宣言をしなければなりません.
ポインター変数の宣言は,次のように書きます.
int *aptr; int型変数のアドレスを記憶するポインター変数aptrの宣言
float *aptr; float型変数のアドレスを記憶するポインター変数aptrの宣言
double *aptr; double型変数のアドレスを記憶するポインター変数aptrの宣言
ポインタ変数の型宣言子は,int *,float *, double *です.また,ポインター変数名には,通常の変数と識別できるように,次のようにポインタ変数であることをイメージさせるような変数名を使います.ただし,それは,決まりではありません.慣用的なものです.
xxxptr, xxxp, xxx_p, p_xxx
以下の2つのプログラム例は,ポインター変数にアドレスを記憶させることができる事を示す例で,例8.3.1がint *型の場合,例8.3.2がdouble *型ポインターの例です.プログラム例は,これまでの説明で十分理解できると思います.
プログラム例 8.3.1 | |
実行結果 |
#include <stdio.h>
void main(void){
int a, b, c;
int *aptr, *bptr, *cptr;
a = 25;
b = 10;
c = a + b;
printf("1)変数a,b,cが作成されたことを確認\n");
printf(" a=%d\n b=%d\n c=%d\n\n", a, b, c);
printf("2)変数a,b,cの番地\n");
/*a,b,cの番地をポインター変数に記憶*/
aptr = &a; bptr = &b; cptr = &c;
printf(" a:%u\n b:%u\n c:%u\n", aptr, bptr, cptr);
}
|
|
1)変数a,b,cが作成されたことを確認
a=25
b=10
c=35
2)変数a,b,cの番地
a:1245052
b:1245048
c:1245044
|
プログラム例 8.3.2 | |
実行結果 |
#include <stdio.h>
void main(void){
double a, b, c;
double *aptr, *bptr, *cptr;
a = 14.5;
b = 2.5;
c = a + b;
printf("1)変数a,b,cが作成されたことを確認\n");
printf(" a=%f\n b=%f\n c=%f\n\n", a, b, c);
printf("2)変数a,b,cの番地\n");
aptr = &a; bptr = &b; cptr = &c;
printf(" a:%u\n b:%u\n c:%u\n", aptr, bptr, cptr);
}
|
|
1)変数a,b,cが作成されたことを確認
a=14.500000
b=2.500000
c=17.000000
2)変数a,b,cの番地
a:1245048
b:1245040
c:1245032 |
|
8.4 変数名の変わりにアドレスを使う |
ここまで読めば,つぎのような疑問を持つようになった皆さんも多いと思います.
1)いったい,アドレスを知って何の役にたつのだろうか?
2)もしかして,変数名のかわりにアドレスがつかえるのでは?
3)その他....
実は,C言語では,変数名の変わりにアドレスを使って演算をすることが出来ます.また,ポインター変数の性質を利用すれば,関数の用途も大きく広がります.
はっきり言えば,ポインター変数(=アドレス)を使えなければ,C言語のプログラムは書けないといっても過言ではないでしょう.
実際の使い方については,次の章で紹介することにして,ここでは,ポインター変数を使うための基礎知識として間接演算子(*)を紹介します.
間接演算子(*,アスタリスク)は,アドレスに作用して,そのアドレスの記憶内容にアクセスする機能を持ちます.プログラム例8.4.1は,間接演算子の作用を紹介するためのプログラムです.この例では,printf()文中で変数a,bのアドレスに作用して,それらの変数の記憶内容を指定するのに使われています.
プログラムとその実行結果をよく見比べてみてください.
プログラム例 8.4.1 | |
実行結果 |
#include <stdio.h>
void main(void){
int a, b;
a=3; b=5;
printf(" a=%d\n", a);
printf(" b=%d\n\n", b);
/*1)アドレスに演算子(*)を作用させる*/
printf("変数a,bのアドレスに*を作用させると,\n");
printf(" *(&a) = %d\n", *(&a));
printf(" *(&b) = %d\n", *(&b));
}
|
|
a=3
b=5
変数a,bのアドレスに*を作用させると,
*(&a) = 3
*(&b) = 5
|
この程度で感心するのはまだ早い.次のプログラムではアドレスを変数への数値の代入や計算につかった例です.もちろん,このような事を可能にするためには間接演算子が必要となります.プログラムと実行結果をよく見比べてみてください.
プログラム例 8.4.2 | |
実行結果 |
#include <stdio.h>
void main(void){
int a, b, c;
/*1)アドレスを使ってa,bに数値を代入*/
*(&a) = 3;
*(&b) = 5;
/*2)変数a,bの内容を確認*/
printf(" a=%d\n", a);
printf(" b=%d\n\n", b);
/*3)アドレスを使って演算*/
*(&c) = *(&a) + *(&b);
/*cの内容を確認*/
printf(" c = %d\n", c);
}
|
|
a=3
b=5
c = 8 |
ここまでの,間接演算子の使い方の例を整理すると,
1)printf()文中で,アドレスの内容を指定して(読み込み)プリントすることができた.
2)変数のアドレスに作用させて,その変数に数値を代入することができた.
3)機能的には1),2)と重複するが,変数のアドレスに作用させて,演算が可能であった.
以上の内容をひとことで言えば,
間接演算子は,変数のアドレスに作用して,その記憶内容を指定する.(アクセスする)ちょっと,わかりにくい表現だと思いますが,頭の中でイメージを整理してみてください.
|
8.5 ポインター変数と間接演算子の組み合わせ |
一時的に変数のアドレスがわかればいいだけなら,特別,ポインター変数を使う必要はないのですが,配列変数のアドレスを扱うときなど,アドレスをポインター変数に記憶させて扱います.
ここでは,前節のプログラム3例をポインタ変数を使って書き換えてみます.前節の内容が理解できていれば特別説明することはないと思います.
プログラム例 8.5.1 | |
実行結果 |
#include <stdio.h>
void main(void){
int a, b;
int *ap;
int *bp;
a = 3 ; b = 5;
ap = &a; bp = &b;
printf(" a=%d\n", a);
printf(" b=%d\n\n", b);
/*1)アドレスに演算子(*)を作用させる*/
printf("変数a,bのアドレスに*を作用させると,\n");
printf(" *ap = %d\n", *ap);
printf(" *bp = %d\n", *bp);
}
|
|
a=3
b=5
変数a,bのアドレスに*を作用させると,
*ap = 3
*bp = 5
|
プログラム例 8.5.2 | |
実行結果 |
#include <stdio.h>
void main(void){
int a, b, c;
int *ap, *bp, *cp;
ap = &a; bp = &b; cp = &c;
/*1)アドレスを使ってa,bに数値を代入*/
*ap = 3;
*bp = 5;
/*2)変数a,bの内容を確認*/
printf(" a=%d\n", a);
printf(" b=%d\n\n", b);
/*3)アドレスを使って演算*/
*cp = *ap + *bp;
/*cの内容を確認*/
printf(" c = %d\n", c);
}
|
|
a=3
b=5
c = 8 |
|
8.6 頭を整理してみよう |
この章でで紹介したことがらを整理してみましょう.
1)変数にはアドレスがある.(この表現,ちょっと変ですが,このほうがわかりやすいので...)
2)変数のアドレスはアドレス演算子&を変数名に作用させて求めることができる.
3)変数のアドレスを記憶する変数をポインター変数と呼ぶ.
4)間接演算子をアドレスに作用させれば,変数の記憶内容にアクセスすることができる.
以上
|
8.7 ......... |
次へ進む前に
ちょっと,あたまをやすませてください.
...............
|
8.8 配列変数とポインタ− |
|