[前へ]   [目次へ]   [次へ]

ポインタ(2)(C/C++)


今回は、実践的に使えるポインタの用法、入門編です。

絶対IDのみでアクセスするポインタは
ネームスペースを超越してアクセスすることができます。
これは、 他の関数の変数であってもアクセスできる ということを意味します。

これまでの内容では、関数は戻り値を使って一個の値を返すことしかできませんでした。
しかし、関数の引数としてポインタ(絶対ID)を受け取ることにより、
複数の値を呼び出し元に返すことが可能となります。

簡単な例を見てみましょう。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
< 28>
< 29>
< 30>
< 31>
< 32>
< 33>
< 34>
< 35>
< 36>
< 37>
< 38>
< 39>
< 40>
< 41>
< 42>
< 43>
< 44>
< 45>
< 46>
#include <stdio.h>

int Input(int *min,int *max,int *avr){
   
/*
   ユーザーが数値以外を入力するまで、入力を受け取る。
   数値以外が入力されたら、
   入力された値の最低値、最高値、平均値、入力回数を返す。

   戻り値:入力された回数

   int *min:[出力]入力された最低値
   int *max:[出力]入力された最高値
   int *avr:[出力]入力された平均値
   */
   
char buf[64]="";
   
int cnt,sum,n;
   fgets(buf,63,stdin);
   
while(sscanf(buf,"%d",&n)<=0){//(1)
      
puts("1回は入力してください");
      fgets(buf,63,stdin);
   }
   sum=n;
   *min=n;
//(2)
   *max=n;
   fgets(buf,63,stdin);
   
for(cnt=1;sscanf(buf,"%d",&n)>0;cnt++){//(3)
      
if(*min>n)*min=n;//(4)
      
if(*max<n)*max=n;
      sum+=n;
      fgets(buf,63,stdin);
   }
   *avr=sum/cnt;
//(5)
   
return cnt;
}

int main(void){
   
int val1,val2,val3,val4;
   
   puts(
"好きなだけ数値を入力してください");
   val1=Input(&val2,&val3,&val4);
//(6)
   
printf("入力回数:%d\n最低値:%d\n最高値:%d\n平均値:%d\n",
            val1,val2,val3,val4);
   
//終了待ち
   
getchar();
   
return 0;
実行例:
好きなだけ数値を入力してください
30
25
77
41
36
-

入力回数:5
最低値:25
最高値:77
平均値:41

入力された数値について、入力回数、最低値、最高値、平均値を求めて表示するプログラムです。
あまり面白いものではないですが、ポインタの用法に注目しながら見ていきます。

(1)18行は、 sscanf 関数の戻り値を使って継続条件としています。
実は sscanf 関数は正常時は「読み出した要素の数」を、エラー発生時は EOF 定数を返すとされています。
条件は「0以下」となっているのですが、これは EOF の値が大抵-1であることを利用したものです。
ようするに、 sscanf 関数の戻り値が1以上、つまり n に値が読み込まれたことを確認するまでこのループは終わりません。

(2)23行はポインタを使っての代入です。詳細は後半で。

(3)26行のループは(1)18行の条件の逆が設定されています。
つまり、 sscanf 関数の読み込みに成功する限り、ループが続くということです。

(4)27〜28行は最低値、最高値を追跡しています。詳細は後半で。

(5)32行は平均値を求めています。

(6)40行で冒頭で定義した Input 関数を呼び出しています。


さて、今回も実行を追跡していきたいと思います。

36行、 main 関数から始まり、(6)40行目です。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int不定(未初期化)main(val2,line 36)
0x0013FF78int不定(未初期化)main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
mainブロック 確保位置ID:0x0013FF70

A    B C     D E F      G H      I J    D'
val1 = Input ( & val2 , & val3 , & val4 )


通し記号種別
Aint不定(未初期化)変数(val1)
B演算子(=)(2項、算術、優先2、結合←)
Cint(*)(int*,int*,int*)0x0040DDB0関数(Input,line 3)
D演算子( () )(その他、優先16、結合→)
E演算子(&)(単項、その他、優先15、結合なし)
Fint不定(未初期化)変数(val2)
G演算子(&)(単項、その他、優先15、結合なし)
Hint不定(未初期化)変数(val3)
I演算子(&)(単項、その他、優先15、結合なし)
Jint不定(未初期化)変数(val4)
D'演算子Dの終点
演算子Dの関数呼び出しを行うために、
式E〜F、式G〜H、式I〜Jの三個を先に解決します。

A    B C     D E        F        G      D'
val1 = Input ( vtemp1 , vtemp2 , vtemp3 )


通し記号種別
Aint不定(未初期化)変数(val1)
B演算子(=)(2項、算術、優先2、結合←)
Cint(*)(int*,int*,int*)0x0040DDB0関数(Input,line 3)
D演算子( () )(その他、優先16、結合→)
Eint*0x0013FF74一時変数(参照先:val2)
Fint*0x0013FF78一時変数(参照先:val3)
Gint*0x0013FF7C一時変数(参照先:val4)
D'演算子Dの終点
引数部分の式は前回同様 & アドレス取得演算子のみなので省略します。
引数部分の式を解決したので、引数のコピーと関数呼び出しが実行され、実行座標が3行目に設定されます。


この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int不定(未初期化)main(val2,line 36)
0x0013FF78int不定(未初期化)main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14



続いてローカル変数を作成し、17行目です。
冒頭の実行例通りに進めようと思うので、ここで30を入力します。
さらに18行目の sscanf 関数は n への代入に成功し、条件は偽になります。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int不定(未初期化)main(val2,line 36)
0x0013FF78int不定(未初期化)main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"30\n"Input(buf,line 15)
0x0013FF00int不定(未初期化)Input(cnt,line 16)
0x0013FF04int不定(未初期化)Input(sum,line 16)
0x0013FF08int30Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


続いて22行目、 sum=n の詳細は省略で23行目です。

A B   C D
* min = n


通し記号種別
A演算子(*)(単項、その他、優先15、結合なし)
Bint*0x0013FF74変数(min)(参照先:main::val2)
C演算子(=)(2項、算術、優先2、結合←)
Dint30変数(n)
優先順位から、演算子A(ポインタ参照、目標 変数B)から処理されます。
変数Bに格納されているのは Input 関数のネームテーブルには登録されていない、
main 関数所属の変数の絶対IDですが、 * ポインタ参照演算子はおかまいなしに参照します。

A      B C
vtemp1 = n


通し記号種別
Aint&不定(未初期化)一時変数(参照先:main::val2)
B演算子(=)(2項、算術、優先2、結合←)
Cint30変数(n)
ここで代入対象になっているのは main 関数所属の val2 です。
そのため、処理終了後の状態は以下のようになります。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int30main(val2,line 36)
0x0013FF78int不定(未初期化)main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"30\n"Input(buf,line 15)
0x0013FF00int不定(未初期化)Input(cnt,line 16)
0x0013FF04int30Input(sum,line 16)
0x0013FF08int30Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


続いて24行目も、同様に max を介して main 関数の val3 に関数越えで代入されます。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int30main(val2,line 36)
0x0013FF78int30main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"30\n"Input(buf,line 15)
0x0013FF00int不定(未初期化)Input(cnt,line 16)
0x0013FF04int30Input(sum,line 16)
0x0013FF08int30Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


続いて25行目、再び数値入力です。実行例同様に25を入力します。
26行目、 for ループの初回式で cnt=1 、 sscanf 関数により n に25が代入されます。
sscanf 関数は解析に成功したので1が返され、ループ続行条件は真になります。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int30main(val2,line 36)
0x0013FF78int30main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"25\n"Input(buf,line 15)
0x0013FF00int1Input(cnt,line 16)
0x0013FF04int30Input(sum,line 16)
0x0013FF08int25Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


続いて27行目、 if(*min>n) です。

A B   C D
* min > n


通し記号種別
A演算子(*)(単項、その他、優先15、結合なし)
Bint*0x0013FF74変数(min)(参照先:main::val2)
C演算子(>)(2項、論理、優先10、結合→)
Dint25変数(n)
優先順位から、演算子A(ポインタ参照、目標 変数B)から処理されます。

A      B C
vtemp1 > n


通し記号種別
Aint&30一時変数(参照先:main::val2)
B演算子(>)(2項、論理、優先10、結合→)
Cint25変数(n)
30>25は真なので、この条件式は真になります。
真になったので、 *min=n が実行されます。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int25main(val2,line 36)
0x0013FF78int30main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"25\n"Input(buf,line 15)
0x0013FF00int1Input(cnt,line 16)
0x0013FF04int30Input(sum,line 16)
0x0013FF08int25Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


続いて、28行目は偽、29行目で sum に加算が行われ、
30行目の数値入力で77を入力し、26行目に戻って cnt++ 、
sscanf 関数の解析に成功するところまで省略です。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int25main(val2,line 36)
0x0013FF78int30main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"77\n"Input(buf,line 15)
0x0013FF00int2Input(cnt,line 16)
0x0013FF04int55Input(sum,line 16)
0x0013FF08int77Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


再び27行目に戻ってきました。
式の評価は先ほどと同手順ですが、今回は 25>77 で偽となります。
偽となった場合は min に格納された絶対IDの変数 val2 は更新されないため、
ループ内のこの一連の式は、ここまでに入力された値の最小値を常に取り続けます。

28行目も27行目と良く似ています。
違いは max を参照することと、不等号の向きが逆ということです。
今回は 30<77 で真となり、 max に格納された絶対IDの変数 val3 が77になります。
先ほどとは逆に、この一連の式は、ここまでに入力された値の最大値を常に取り続けます。

引き続き、 sum への加算、次に41を入力し、ループを続行します。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int25main(val2,line 36)
0x0013FF78int77main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"41\n"Input(buf,line 15)
0x0013FF00int3Input(cnt,line 16)
0x0013FF04int132Input(sum,line 16)
0x0013FF08int41Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


三度27行目です。
今回は、27、28行目ともに条件式は成立しません。
次に36を入力し、さらにループを続行します。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int25main(val2,line 36)
0x0013FF78int77main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"36\n"Input(buf,line 15)
0x0013FF00int4Input(cnt,line 16)
0x0013FF04int173Input(sum,line 16)
0x0013FF08int36Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


今回も27、28行目の条件式は成立せず、 sum への加算が行われます。
続く入力で - を入力して、入力の終了を示します。
これにより、 cnt へのカウントアップ後の sscanf 関数が失敗、ループが終了します。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int25main(val2,line 36)
0x0013FF78int77main(val3,line 36)
0x0013FF7Cint不定(未初期化)main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"-\n"Input(buf,line 15)
0x0013FF00int5Input(cnt,line 16)
0x0013FF04int209Input(sum,line 16)
0x0013FF08int36Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


ループを離脱したので(5)32行目、 *avr=sum/cnt です。

A B   C D   E F
* avr = sum / cnt


通し記号種別
A演算子(*)(単項、その他、優先15、結合なし)
Bint*0x0013FF7C変数(avr)(参照先:main::val4)
C演算子(=)(2項、算術、優先2、結合←)
Dint209変数(sum)
E演算子(/)(2項、算術、優先13、結合→)
Fint5変数(cnt)
優先順位から、演算子A(ポインタ参照、目標 変数B)から処理され、
avr に格納された絶対IDの変数が式に現れます。

A      B C   D E
vtemp1 = sum / cnt


通し記号種別
Aint&不定(未初期化)一時変数(参照先:main::val4)
B演算子(=)(2項、算術、優先2、結合←)
Cint209変数(sum)
D演算子(/)(2項、算術、優先13、結合→)
Eint5変数(cnt)
続いて、演算子D(除算、左辺 変数C、右辺 変数E)です。
int 型同士なので、割り算の結果は端数切捨てで41となります。

A      B C
vtemp1 = vtemp2


通し記号種別
Aint&不定(未初期化)一時変数(参照先:main::val4)
B演算子(=)(2項、算術、優先2、結合←)
Cint41一時変数
関数を越えて41が代入されます。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int25main(val2,line 36)
0x0013FF78int77main(val3,line 36)
0x0013FF7Cint41main(val4,line 36)
0x0013FF14int*0x0013FF74(main::val2)Input(min,line 3)
0x0013FF18int*0x0013FF78(main::val3)Input(max,line 3)
0x0013FF1Cint*0x0013FF7C(main::val4)Input(avr,line 3)
0x0013FEC0char[64]"-\n"Input(buf,line 15)
0x0013FF00int5Input(cnt,line 16)
0x0013FF04int209Input(sum,line 16)
0x0013FF08int36Input(n,line 16)
mainブロック 確保位置ID:0x0013FF70
Input引数 確保位置ID:0x0013FF14
Inputブロック 確保位置ID:0x0013FEC0


そして33行目で return cnt され、ようやく呼び出し元に戻ります。
それに伴い、 Input 引数と Input ブロックスコープのローカル変数が破棄されます。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int不定(未初期化)main(val1,line 36)
0x0013FF74int25main(val2,line 36)
0x0013FF78int77main(val3,line 36)
0x0013FF7Cint41main(val4,line 36)
mainブロック 確保位置ID:0x0013FF70

呼び出し元、40行目の処理を続行します。

A    B C
val1 = vtemp1


通し記号種別
Aint不定(未初期化)変数(val1)
B演算子(=)(2項、算術、優先2、結合←)
Fint5一時変数
そのまま代入されます。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF70int5main(val1,line 36)
0x0013FF74int25main(val2,line 36)
0x0013FF78int77main(val3,line 36)
0x0013FF7Cint41main(val4,line 36)
mainブロック 確保位置ID:0x0013FF70

そして最後に、 printf 関数での表示です。

A      B C                                                      D      E      F      G    B'
printf ( "入力回数:%d\n最低値:%d\n最高値:%d\n平均値:%d\n" , val1 , val2 , val3 , val4 )


通し記号種別
Aint(*)(const char*,...)0x00401400関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[47]"入力回数:%d\n最低値:%d\n最高値:%d\n平均値:%d\n"文字列定数
Dint5変数(val1)
Eint25変数(val2)
Fint77変数(val3)
Gint41変数(val4)
B'演算子Bの終点


これで冒頭の例の動作詳細通りの出力が行われ、
プログラムは終了処理に入っていきます。

今回のポイントは、
「ポインタ変数の持つ絶対IDを介して main 関数側のローカル変数を逐一書き換えている」
という部分です。
このように、ポインタを用いることで処理を行う関数のネームテーブルでは見えない変数に対してアクセスすることができます。
また、呼び出し時に与える絶対IDを変更すれば、もちろんアクセス対象の変数は変化します。

次回も引き続き、ポインタ使用の実例と詳細追跡を行う予定です。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2008/10/17