Programmers Room Tips集 
プログラム基礎編 更新[2000.04.10]

このページは、メールマガジン「実戦・C/C++言語」に掲載された内容をPick UPし、再編集して掲載しております。

Contents

1.ポインタ1 [2000.04.02]
2.ポインタ2 [2000.04.10]

1.ポインタ 1[2000.04.02]

C言語を学び始めた人が最初にぶつかる難関はどこでしょうか?
構造体だと言う人もいれば、そもそも関数の考え方が分からないといった人もいらっしゃるかもしれません。
でも一番つまずき易いのが、なんといってもポインタではないでしょうか?
ちなみに私も、C言語をはじめて最初の2年ぐらいは、あまり良く分かっていなくて、なるべく使わないようにするか、「形式」で丸暗記して使用していました。

丸暗記とは、例えば関数に渡して、関数内で中身を変更して返して欲しかったら呼び出し側に&をつけて、関数には*をつければよい。なぜそれで出来るかは、考えなかったというか分からなかったのです。

 short a; // なんだかいつもa
 func(&a);
 ---------
 void func(short *a)
 {
         *a = 5;
 }

のように書けば、funcからaに値を返すことが出来ると、単純に憶えていたのです。

ここでは上記が「なぜか」を考えてみましょう。

まず、short a; // なぜかa
と定義すると、プログラムを実行した際に、メモリ上に2バイトの領域が取られます。(Windowsで実行したとします。)

アドレス 中身
アドレス 中身  
1234 0a aの中身(下位バイト)
1235 21 aの中身(上位バイト)
1236 23 (次のアドレスは別の変数等が使っていることでしょう)
1237  

この表はメモリです。(ビルのフロア断面図では無いです。)。中身の列の一つのマス目一つが一バイトです。
たまたま、1234番地からaの値が設定されたとします。aはshortなので、2バイトですから1235番地もaの領域です。

0aと21という値が入っています。この値は何でしょうか?
実はでたらめな値です。今私が考えました。でもそれで良いのです。
  short a;
と宣言したときこのaの中身2バイトには何が入っているか保証されません。
正確には、そのメモリがshortのaとして使用される前の残骸が残っているわけです。
デバッカなどで、定義した直後にその値の中身を見てみると、とんでも無い値が入っていることが多いのはそういう事です。

ところで、このときのaの値はいくつでしょう。
Windowsの場合、「リトルエンディアン」といって、数値のバイトは前後が逆になるので、
  a は 0x210a 、10進数で 8458
です。

さて、話を元に戻しますが、ここで、それぞれの場所におけるaの値について
考えていきましょう上記のメモリだと、
  short a; // やっぱ例題はアルファベットの始まり、a
と定義したとき、aのアドレス&aの値はいくつでしょうか?
&aは1234になります。

関数funcはfunc(short *a)と定義されているわけだから、関数funcの中におけるaは、ポインタなのです。だから

funcの中では、aの値は1234です。

そして、宣言以外のところで、ポインタの頭に*をつけると、ポインタの中身を表すことになりますので

 funcの中では、*aの値は8458です。

そして、

 *a = 5;

とすることにより、ポインタaの中身に5が代入されるわけです。

まとめましょう。

void main()
{
        short a;                        
// なんだかいつもa
        printf("\naの値=%d.", a); 
// aに何が入っているか不明。(1)
        func(&a);                       // func()にaのアドレスを渡す(2)
        printf("\naの値=%d.", a);  // 表示してみる。(4)
}

// 受け取ったポインタの中身に5を代入する。
// こんな場合外に全く用途の無い関数
void func(short *b) 
{
        *b = 5;          
// ポインタaの中身に、5を代入する。(3)
}}

  1.  上記にも記述した通り、aの中身は定義した直後には、何が入っているかわからない。
  2.  aのアドレスを渡す。通常の値を渡す場合と違い、「入っている場所(上記メモリの例だと1234)」を渡す。
  3.  ポインタ(入っている場所)を引数で受けたので、「その中身」に5を代入する。すなわち、mainのaとfunc()内の*aは全く同じものである。
  4.  (3)でfuncがaの「中身」に5を代入したので、ここで5が表示される。

ちなみに、この関数を

void main()
{
        short a;                        
// なんだかいつもa
        printf("\naの値=%d.", a); 
// aに何が入っているか不明。(1)
        func(a);                       // func()にaのアドレスを渡す(2)
        printf("\naの値=%d.", a);  // 表示してみる。(4)
}

// 受け取ったポインタの中身に5を代入する。
// こんな場合外に全く用途の無い関数
void func(short b) 
{
        b = 5;
}}

とした場合、(4)で、5は表示されません。なぜならば、この関数の引数を、short bとして定義すると、bはアドレスでは無いので、aとは別なアドレスにb用の領域が出来てしまい、呼ばれたときに、呼び出し元のaの中身がbに代入されるだけです。よってaと別の場所にあるbに何を代入しても、aの値は変化しません。このように実体を渡すのは、関数に対して、一方通行に値を伝えることしか出来ないのです。

ようするに、変数を箱に例えるなら、

アドレスを渡す ==> 箱のある場所を教える。
実体を渡す ==> 相手の箱に、自分の中身と同じものを入れてやる。

という事なのです。

2.ポインタ 2[2000.04.10]

配列に数値を10件入力する以下のようなプログラムがあったとします。

  int a[10];
  char szBuff[10+1];
  for(int i=0;i<10;i++){
     printf("\n%d番目の数値を入力してください=>",i + 1);
     gets(szBuff);
     a[i] = atoi(szBuff);
  }

これをポインタで書くとどうなるでしょう。

void func()
{
    
int  a[10];
    
int *pa;
    
char szBuff[10 + 1];
    pa = a; 
//配列aの先頭アドレスをポインタpaに代入

    
for(int i=0;i<10;i++,pa++){
        printf(
"\n%d番目の数値を入力してください=>", i + 1);
        gets(szBuff);
        *pa = atoi(szBuff);
    }
}

となります。

配列int a[10];の先頭、のアドレスをどう現すかといいますと、配列のポインタは[]を取った状態です。
だからpa = a;で、配列aの「アドレス」をポインタpaに代入したわけです。
すなわち、pa は &a[0] と同じです。
だからforの中でpa++とすることでaのアドレスを一つ進めることになります。
1回pa++とすると、pa は &a[1] と同じになります。
 
もっとも、実際は上記のように、どっちでも良い状況で、無理にポインタを使用する必要は無いと思います。

では、ポインタは、どのような場合に使用されるのでしょうか?
比較的に多いのは、動的確保、C言語でいうmalloc,free, C++におけるnew,deleteです。
 
以下は、記述を簡略化するために、C++で書きますが、論理的には、C言語でmalloc,free関数を使用する場合も同様です。

 

  char* a;  // char型のポインタを宣言

  a =
new char[100];  // ポインタaに100バイトの領域を割り当てる。
   
  
// <略。この間aを色々なことに使用することでしょう。>
   
  
delete[] a;     // 用が済んだので削除

char* a; とやった段階で、aはキャラクタ型で使用するポインタとして宣言されたのですが、ここでaの値は、以前に、変数のお話でも触れましたが、ポインタの場合も同様で、何が入っているかわかりません。

そして、a = new char[100];で、100バイトの領域が確保されるのですが、ここでは何が起こっているのでしょうか?
プログラムが使用しているメモリ領域のどこかに、100バイトの領域が変数で使用できるように確保される。その確保された100バイトの領域の先頭アドレスがポインタaに代入された。
という事です。

最後にdelete[] a;で確保した領域を、解放する(他の処理で使用できるように未使用領域にする。)わけですが、delete[] a;は領域を開放(delete)それは配列で([])アドレスaから始まります。ということです。


[TOPへもどる] [Soft Page TOPへもどる] [ProgramerS Room Topへ] [このページのTop]