ポインターを学習する前に,ハードウェアーについて説明した.
- 図4がコンピューターの基本構成である.
- 制御装置と演算装置,記憶装置,入力装置,
出力装置をコンピューターの五大装置と呼び,それぞれには以下の働きがある.
- 制御装置
- 主記憶装置(メインメモリー)に格納されているプログラムを
受け取り,各装置に指令を出す.
- 演算装置
- 主記憶装置の中にあるデータを受け取り,制御装置の指令に
従い,それを処理する.
- 記憶装置
- 主記憶装置と補助記憶装置がある.
- 主記憶装置
- 命令とデータからなるプログラムを格納する.
- 補助記憶装置
- 大容量,あるいは半永久的に残したいデータを補助記憶
装置に格納する.
- 入力装置
- コンピューターの外部からデータを取り込む装置である.
- 出力装置
- 主記憶装置に格納されているデータをコンピューター外部に
出力する装置である.
- 制御装置と演算装置をまとめて中央制御装置(CPU:Central Processing Unit)ある
いは, MPU(Micro Processing Unit) と言う.
- 2進数の1桁が1ビットである.
- 一つのアドレスに8ビット(1バイト)記憶できる.これは,16進数2桁で表現できる.
- 8ビットを1バイトと言う.
- 諸君が使っているパソコンのアドレスは32ビットで表現されている.これは16進
数では8桁である.したがって,アドレスを記憶するためには4バイト--メモリー
の4番地分--必要である.
- 諸君が使っているコンパイラーでは,型は表3に示し
ているバイト数で表現されている.
表 3:
変数の型とバイト数
型名 |
データ型 |
バイト数 |
ビット数 |
文字型 |
char |
1 |
8 |
整数型 |
int |
4 |
32 |
倍精度実数 |
double |
8 |
64 |
- プログラムは命令とデータから構成され,いずれもメモリーの中に格納される.
- プログラムの関数(これが命令)が格納されるアドレスは,関数名で参照できる.
- ローカル変数は名前が同じでも,メモリーの配置場所は異なる.
- アドレスを格納するための変数をポインター変数2という.それは,
int *pi;
double *px;
と宣言する.アスタリスク(*)をつければ,ポインター変数の宣言になる.
- 変数のアドレスは,アドレス演算子(&)により取り出すことができ
る.たとえば,整数型変数 i と実数型変数 x のアドレスは,
&iと&xとすると取り出すことができる.ここで,アドレス演算子
&は,それに引き続く変数の先頭アドレスを取り出す演算子である.取り出
したアドレスは,ポインターに
pi=&i;
px=&x;
のようにして代入できる.アドレス演算子(&)により変数の先頭アドレスを取り出
して,代入演算子(=)を用いて,ポインター変数に代入している.アドレス
を表示するときには,変換指定子%pを使う.
printf("%p",&x);
- ポインター機能は,アドレスの格納のみに止まらず,そのアドレスが示しているデータの
内容も表すことができる.今までの例の通り,ポインター変数には変数の先頭アドレスが格納
されている.そして,ポインターの宣言の型から,そのポインターが指しているデータの
内容までたぐり寄せることができる.ポインターpiとpxが示しているデータの
値を,整数型変数 j と実数型変数 y に代入する場合
j=*pi;
y=*px;
と書く.ここで,アスタリスク(*)は間接参照演算子3で,ポインターが示しているア
ドレスのデータを取り出す演算子である.このようにアドレスのみならず,そのアドレス
のデータの型までポインターは持っているから,これが可能なのである.このことから,
アドレスとは言わずにポインター(pointer 指し示すもの)と言うのであろう.
- 紛らわしいことに,間接参照演算子と積の演算子は同じアスタリスク(*)を
つかう.C言語の悪いところだが,そうなってしまっているので仕方ない.コンパ
イラーは前後の式からどちらなのか判断している.
ポインターに関係する演算子を表
4にまとめておく.ただし,各変数は
double x, *xp;
と宣言したとする.
表 4:
普通の変数とポインター変数に演算子を作用させた場合に取り出せる値.
演算子 |
通常の変数(x) |
ポインター変数(xp) |
例 |
無し |
格納されている値 |
格納されているアドレス |
x,xp |
& |
変数のアドレス |
ポインター変数のアドレス |
&x,&xp |
* |
コンパイルエラーのため不可 |
ポインターが示すアドレスに格納されている値 |
*xp |
リスト2のプログラムをよく理解すること.アドレスとポイ
ンターの関係や演算子の使い方を分からなくてはならない.
- 4行
- 整数型のポインターpを宣言している.pに整数型のデータの先頭
アドレスを格納する.
- 5行
- 整数型の変数 i を宣言し,
を代入している.
- 7行
- 変数 i の先頭アドレスをアドレス演算子 &により取り出し,ポインター
p に代入している.
- 9行
- 整数変数 i の先頭アドレスを変換指定子 %p により表示している.
- 10行
- ポインター p の先頭アドレスを変換指定子 %p により表示している.
- 12行
- 整数変数 i の値を16進数表示の変換指定子 %0x により表示している.
- 13行
- ポインター p の値を16進数表示の変換指定子 %0x により表示してい
る.ただし,ポインターはアドレスなので,強制型変換(キャスト)により,
符号なし整数にしている.
- 15行
- ポインターが指し示すアドレスに格納されているデータを表示している.
1 #include <stdio.h>
2
3 int main(void){
4 int *p;
5 int i=0x11223344;
6
7 p=&i;
8
9 printf("address i %p\n", &i);
10 printf("address p %p\n", &p);
11
12 printf("value i %0x\n", i);
13 printf("value p %0x\n", (unsigned int)p);
14
15 printf("value *p %0x\n", *p);
16
17 return 0;
18 }
address i 0xbffff6b0
address p 0xbffff6b4
value i 11223344
value p bffff6b0
value *p 11223344
この実行結果から,メモリーは図
5のようになっていること
が分かる.
図 5:
リスト2のプログラム実行後のメモリーの内容
|
ポインターは,整数型の値の和と差の演算ができる.しかし,積と商の演算はできない.
ポインターの和や差の演算には,どういう意味があるのだろうか? リスト
3がそれに対する答えである.ポインターの演算--整数の加算
と減算--は,データの型のバイト数分,ポインターがシフト(移動)する.プログラムの
実行結果から分かるように,
-4pt
- 文字型のポインターでは,整数の1加算はアドレスの値が1増える.
- 整数型のポインターでは,整数の1加算はアドレスの値が4増える.
- 倍精度実数型のポインターでは,整数の1加算はアドレスの値が8増える.
となっている.
ポインターに加算される整数は,ポインターが指し示すデータの移動量を表す.ひとつの
アドレスには1バイトのデータが格納できる.そして,文字型のデータでは1バイト,整数
型では4バイト,実数型では8バイトのメモリーである.これが,型に依存して,アドレス
の変化の仕方が異なった理由である.
1 #include <stdio.h>
2
3 int main(void)
4 {
5 char *cp;
6 int *ip;
7 double *dp;
8 int i;
9
10 for(i=0; i<4; i++){
11 printf("%d %p\t%p\t%p\n",i, cp+i, ip+i, dp+i);
12 }
13
14 return 0;
15 }
0 0xbff0eb9c 0xbff0eb08 0x8048416
1 0xbff0eb9d 0xbff0eb0c 0x804841e
2 0xbff0eb9e 0xbff0eb10 0x8048426
3 0xbff0eb9f 0xbff0eb14 0x804842e
関数呼出しの時,次のふたつの方法で値--処理すべきデータ--を渡すことができる.
- 値そのものを渡す方法を値渡し(call by value)と言う.呼出側の実引数の
値は,呼び出された関数の仮引数(変数)にコピーされる.この方法では,呼出側
の関数と呼び出された側の関数のそれぞれが,記憶領域--変数--を持つので,
関数の独立性が高くなる.すなわち,呼び出された側で,呼出側の変数の値を変
えることができない.
- アドレスを渡す方法を参照渡し(call by reference)と言う.呼出側の引数
はアドレスである.呼び出された関数の仮引数のポインターにそのアドレスがコ
ピーされる.この方法では,呼び出された側の関数で呼出側の関数のデータ領域
の操作ができる.そのため,関数の独立性が失われる.すなわち,呼び出された側
で,呼出側の変数の値を変えることができる.
参照渡しを使うと,グローバル変数を使わないで複数の計算結果を呼出元へ知らせること
ができる.リスト4の関数cal()は,和と差,積,商を
一度に計算し,参照渡しを使い4つの値を一度に呼出元へ知らせている.実際には,呼出し元
から指定されたアドレスに,呼び出された関数cal()が計算結果を書き込んでいるの
である.
1 #include <stdio.h>
2
3 void cal(int *wa, int *sa, int *seki, int *sho, int a, int b);
4
5 //----------------------------------------------------------
6 int main(void){
7 int add, sub, mul, div;
8
9 cal(&add, &sub, &mul, &div, 33, 3);
10 printf("add = %d\n", add);
11 printf("sub = %d\n", sub);
12 printf("mul = %d\n", mul);
13 printf("div = %d\n", div);
14
15 return 0;
16 }
17
18 //----------------------------------------------------------
19 void cal(int *wa, int *sa, int *seki, int *sho, int a, int b){
20
21 *wa = a+b;
22 *sa = a-b;
23 *seki = a*b;
24 *sho = a/b;
25 }
add = 36
sub = 30
mul = 99
div = 11
関数の引数に配列名を使う場合,参照渡しとなる.配列名は配列の先頭アドレスを表すポ
インターとなっているからである.すなわち,配列名という変数(のようなも
の)には,その配列の先頭アドレスが格納されている.その配列名--アドレスの値--を
渡すのであるから,参照渡しである.
リスト5に配列を関数に渡す例を示す.この例を見て分かるように,配列を
渡す場合は次のようにする.
- 呼出側の実引数は配列名のみを書く.配列のサイズを書いてはならない.配列名
は配列の先頭アドレスが格納されたポインターなので,これにより呼び出された関
数へアドレスを渡すことができる.
- 呼び出された関数の仮引数は,配列名にかぎかっこ[]をつける.
- 配列のサイズが呼び出された関数で必要であれば,別の引数を使う.
リスト5の関数reverse()は,配列に格納されている順序を逆に
する関数である.
1 #include <stdio.h>
2 void reverse(int n, int a[]);
3
4 //--------------------------------------------
5 int main(void){
6 int hoge[3]={1,2,3};
7
8 printf("hoge=\t%d\t%d\t%d\n",hoge[0],hoge[1],hoge[2]);
9 reverse(3, hoge);
10 printf("hoge=\t%d\t%d\t%d\n",hoge[0],hoge[1],hoge[2]);
11
12 return 0;
13 }
14
15 //-------------------------------------------
16 void reverse(int n, int a[]){
17 int i, i_max, temp;
18
19 i_max=n/2;
20
21 for(i=0; i<i_max; i++){
22 temp=a[i];
23 a[i]=a[n-1-i];
24 a[n-1-i]=temp;
25 }
26 }
hoge= 1 2 3
hoge= 3 2 1
ホームページ:
Yamamoto's laboratory著者:
山本昌志
Yamamoto Masashi
平成19年3月4日