5 ポインター

5.1 基本

ポインターとは何か?--と問われると,最も適切な答えは「ポインターはオブジェクトの アドレスである」であろう.そうするとオブジェクトとは何か?--という ことになる.オブジェクトとはメモリー上のデータ領域のことでアドレスがあり,型を持 つのでサイズもある.ポインターはオブジェクトの先頭アドレスを示すことにより,オブ ジェクトを指し示す.実際には,ポインター変数にオブジェクトの先頭アドレスを格納し ているのである.先頭アドレスのみならず,オブジェクトの大きさもどこかに情報として 持っていることを忘れてはならない.

このことを具体例をつかって,説明しよう.リスト7のプログ ラムでは,pがポインター変数でhogeがオブジェクトである.この関係は図 1のように表すことができる.

   1 #include <stdio.h>
   2 
   3 int main(void)
   4 {
   5   int hoge=3;
   6   int *p;
   7 
   8   p=&hoge;
   9 
  10   printf("hoge=%d\n",*p);
  11 
  12   return 0;
  13 }


\fbox{実行結果}
hoge=3
図 1: ポインターpとオブジェクトhogeの関係.
\includegraphics[keepaspectratio, scale=1.0]{figure/pt_for_hoge.eps}

このような結果が得られるのは,リスト7の10行目でポインター phogeを指し示すからである.オブジェクトとポインターの関係は8行目で hogeで決められている; オブジェクトの先頭アドレス3 をポインター変数に代入している.

5.2 さまざまなポインター

5.2.1 構造体へのポインター

構造体のポインターの例をリスト8にしめす.図 2にポインターと構造体の関係を示す.リストを見て分かるように, 構造体へのポインターは次のようにして使う. -4pt
   1 #include <stdio.h>
   2 
   3 int main(void)
   4 {
   5   typedef struct{                        // 構造体の定義
   6     char name[16];
   7     int math;
   8     int info;
   9   }student;
  10 
  11   student yama={"yamamoto",72,83};       // 宣言と初期化
  12   student *p;                            // 構造体 student 型へのポインター
  13 
  14   p=&yama;                               // 先頭アドレスの代入
  15 
  16   printf("name = %s\n", p->name);        // メンバーへのアクセスは,アロー演算子
  17   printf("math = %d\n", p->math);
  18   printf("info = %d\n", p->info);
  19 
  20   return 0;
  21 }


\fbox{実行結果}
name = yamamoto
math = 72
info = 83
図 2: ポインターpと構造体のオブジェクトstudentの関係.
\includegraphics[keepaspectratio, scale=1.0]{figure/pt_struct.eps}

5.3 関数へのポインター

諸君にとって,最もけったいに感じるのが関数へのポインターであろう.実行すべき命令 が書かれている関数もメモリーにロード--読み込んで格納--される.したがって,先 頭アドレス--関数のエントリー--をポインターに代入することができる.
   1 #include <stdio.h>
   2 
   3 int add(int i, int j);             // プロトタイプ選言
   4 
   5 //============ メイン関数===============================
   6 int main(void)
   7 {
   8   int (*fp)(int, int);             // 関数へのポインター
   9 
  10   fp=add;                          // 関数のアドレスを代入
  11 
  12   printf("%d\n",fp(5,9));
  13   
  14 
  15   return 0;
  16 }
  17 
  18 //============ ユーザー定義関数==========================
  19 int add(int i, int j)
  20 {
  21   return i+j;
  22 }


\fbox{実行結果}
14

これは使い方によってはかなり便利である.サブルーチンへ関数を渡すことが可能となる. リスト10がそれを使った例である.ここで,関数定義の仮引数の double (*f)(double)が関数へのポインターの宣言で,

戻り値の型 (*関数へのポインター変数)(引数の型)
と書く.この関数へのポインター変数に関数のポインター--先頭アドレス--を代入する と,ポインター変数が関数のように使える.関数のポインターへの代入は関数名を右辺値 として,代入するだけである.
   1 #include <stdio.h>
   2 #include <math.h>
   3 
   4 void print_func(double (*f)(double));  // プロトタイプ選言
   5 
   6 //======== メイン関数 ========================================
   7 int main(void)
   8 {
   9 
  10   print_func(sin);
  11   print_func(cos);
  12 
  13   return 0;
  14 }
  15 
  16 //========== 関数の値を表示する関数 ============================
  17 void print_func(double (*f)(double))
  18 {
  19   int i;
  20   double dx=0.1;
  21 
  22   printf("--------------------------\n");
  23   for(i=0; i<=5; i++){
  24     printf("%f\t%f\n", i*dx, f(i*dx));
  25   }
  26 }


\fbox{実行結果}
--------------------------
0.000000        0.000000
0.100000        0.099833
0.200000        0.198669
0.300000        0.295520
0.400000        0.389418
0.500000        0.479426
--------------------------
0.000000        1.000000
0.100000        0.995004
0.200000        0.980067
0.300000        0.955336
0.400000        0.921061
0.500000        0.877583

5.4 ポインターの配列

複数組の文字列を扱う場合,ポインターの配列は便利である.リスト 11にプログラム例を,図3にポインターと文字列定 数の関係を示す.ここでの動作は次のようになる.
   1 #include <stdio.h>
   2 
   3 int main(void)
   4 {
   5   char *animal[]={"cat", "dog", "rabbit", "horse"};
   6 
   7   printf("1st : %s\n", animal[0]);
   8   printf("2nd : %s\n", animal[1]);
   9   printf("3rd : %s\n", animal[2]);
  10   printf("4th : %s\n", animal[3]);
  11 
  12   return 0;
  13 }


\fbox{実行結果}
1st : cat
2nd : dog
3rd : rabbit
4th : horse
図 3: 文字列定数とポインターの配列animalの関係.
\includegraphics[keepaspectratio, scale=1.0]{figure/pt_array.eps}

5.5 メモリー配置

プログラムを実行するとき,メモリーはいろいろなデータがある.プログラム実行に必要 なデータは4つのメモリー領域に分けて格納される.

C言語では大雑把に言って,コード(code)、データ(data)、ヒープ(heap)、スタック (stack)の4つの領域にメモリーを分けて,管理する.これらの使い分けに,プログラマー はほとんど気にする必要はない.ただし,変数--配列や構造体を含む--を使う場合, メモリーは次のような使い方があると,プログラマーは認識しておくべきである. -4pt

5.6 動的メモリーの確保

大量のデータを処理するために,大きな配列をローカル変数で宣言するとコンパイルはで きても,実行時にエラーとなることがある.大きなメモリー使えない理由は,スタック領 域が狭いことによる.先に述べたようにローカル変数はスタック領域を使うため,それに 制限されるのである.

大きな配列を使いたい場合,ヒープ領域をつかう.malloc()関数によりメモリーを 確保して,free()関数によりメモリーを開放する.その例をリスト 12に示す.

   1 #include <stdio.h>
   2 #include <stdlib.h>
   3 
   4 int main(void)
   5 {
   6   int *a;
   7 
   8   a=malloc(sizeof(int)*1024*1024*10);
   9 
  10   a[0]=1;
  11   printf("a[0]=%d\n",a[0]);
  12 
  13   free(a);
  14 
  15   return 0;
  16 }


\fbox{実行結果}
a[0]=1
malloc()関数を使って,メモリーを確保している.ただし,この関数でいつも思い 通りのメモリーを確保できるとは限らない.この関数は,メモリー確保に失敗した場合,NULLポ インターを返す.
ホームページ: Yamamoto's laboratory
著者: 山本昌志
Yamamoto Masashi
平成19年6月7日


no counter