ポインタの話

はじめに

これは何?

C言語ではポインタの話を避けて通れません. ちうわけでちょこっと説明してみました. すべての文責は高橋にあります.

計算機アーキテクチャ的にはええかげんな説明になってますので気をつけましょう.

2022-06-06 追記: 表示の仕組みのせいで一部のコードのインデントがめちゃくちゃになってます.ごめんなさい.

更新履歴

関数を使うときにポインタが必要になるのはなんで?

いきなりですが,以下の二つのプログラムの実行結果の違いとその理由を説明できますか?できない人は,それぞれのリンクをたどってふるまいを調べてみませう.

違いがわかる人は,「printf()には&はいらんのにscanf()には必要」なんがなんでかわかりますね.

プログラム例その一

このプログラムのふるまいをみる

     1  void hoge(int x, int y);
     2  	
     3  int main()
     4  {
     5     int a, b;
     6	
     7	     a = 7; b = 3;
     8	     hoge(a, b);
     9	     printf("%d %d\n", a, b);
    10  }
    11	
    12  void hoge(int x, int y)
    13  {
    14     y = x + y;
    15     printf("%d %d\n", x, y);
    16  }

プログラム例その二

このプログラムのふるまいをみる

     1  void hoge(int x, int *y);
     2	
     3  int main()
     4  {
     5     int a, b;
     6	
     7     a = 7; b = 3;
     8     hoge(a, &b);
     9     printf("%d %d\n", a, b);
    10  }
    11	
    12  void hoge(int x, int *y)
    13   {
    14     *y = x + *y;
    15     printf("%d %d\n", x, *y);
    16   }

なんで配列の受け渡しんときはポインタ使わん(ように見える)のか

関数への値の受け渡しでのポインタの使い方がわかってくると,「ほななんで配列の受け渡しはあんなんでええのん?」という疑問がわいてきます.

配列を使った例として以下のソースを考えましょう.

● プログラム例 fuga1.c

     1   void fuga(int x[], int n);
     2	
     3   int main()
     4   {
     5      int a[10];
     6      int n = 3, i;
     7	  
     8      for(i = 0; i < n; i++){
     9         a[i] = 2*i + 1;
    10         printf("%d %d\n", i, a[i]);
    11      }
    12      printf("-----\n");
    13	
    14      fuga(a, n);
    15	
    16      for(i = 0; i < n; i++){
    17         printf("%d %d\n", i, a[i]);
    18      }
    19   }
    20	
    21   void fuga(int x[], int n)
    22   {
    23      int i;
    24	
    25      for(i = 0; i < n; i++) x[i] *= 3;
    26   }

このプログラムを実行すると,以下のような結果が得られます.

0 1
1 3
2 5
-----
0 3
1 9
2 15

関数fuga()によって配列aの中身が変更されていることがわかります.配列ではない変数の場合には,関数の中での値の変更をmain()の側にも反映させるためにはポインタを使う必要がありました.しかしこの例では,ポインタを使っているように見えないのに,関数中での変更が外の世界にも反映されています.一体どうなっているのでしょうか.

実は,

ということがみそになっています.

以下のソースを例にしてこれを説明してみます.これは上記のソースを少し書き換えたものですが,実行すると上記のものと同じ結果となります.

● プログラム例 fuga2.c

     1   void fuga(int x[], int n);
     2	
     3   main()
     4   {
     5      int a[8];
     6	      int n = 3, i;
     7	  
     8      for(i = 0; i < n; i++){
-->  9      *(a + i) = 2*i + 1;
--> 10      printf("%d %d\n", i, *(a + i));
    11      }
    12      printf("-----\n");
    13	
    14      fuga(a, n);
    15	
    16      for(i = 0; i < n; i++){
    17         printf("%d %d\n", i, a[i]);
    18      }
    19   }
    20	
    21   void fuga(int x[], int n)
    22   {
    23      int i;
    24	
    25      for(i = 0; i < n; i++) x[i] *= 3;
    26   }

この例の5,9,10行目を説明すると,

  1. 5行目の配列の宣言で,メモリ中にint型のデータ八つ分の場所が確保されている.例えば0x1000番地,0x1004番地,...,0x101c番地ちう感じ.
  2. a が配列の先頭番地(この場合 0x1000)を表すので,a + i はこの配列のi番目の要素の番地を表すことになる(a+1 は 0x1004,a+2 は 0x1008).
  3. したがって *(a + i) は a + i 番地の中身,すなわち「配列の先頭から i 番目の値」である.つまり, *(a + i) = a[i]

ということになっています(注:二項演算子 + よりポインタのための演算子 * の方が優先順位が高いので,*a + i と書くと違う結果になります).

C言語のコンパイラは,a[i] というソースを見たら *(a + i) に置き換えて処理しているので,当然 fuga1.c も fuga2.c も同じ結果となるわけです(ちなみに,*(a + i) = *(i + a) ですから,配列の名前と添字を引っくり返して i[a] とか書いても平気やったりします.3[a] とか書いても平気やったりします.気持悪いけど...).

以上のことから,配列データを普通に関数と受け渡しすると関数中での変更が外の世界にも反映される理由がわかります.だって知らん間にポインタ使うてて,値を渡すんやのうて番地渡してんねやからね. fuga2.c をさらに書き換えて以下のようにしてみればはっきりします.

● プログラム例 fuga3.c

     1	void fuga(int x[], int n);
     2	
     3	main()
     4	{
     5	  int a[10];
     6	  int n = 3, i;
     7	  
     8	  for(i = 0; i < n; i++){
     9	    *(a + i) = 2*i + 1;
    10	    printf("%d %d\n", i, *(a + i));
    11	  }
    12	  printf("-----\n");
    13	
    14	  fuga(a, n);
    15	
    16	  for(i = 0; i < n; i++){
    17	    printf("%d %d\n", i, a[i]);
    18	  }
    19	}
    20	
--> 21	void fuga(int *x, int n)
    22	{
    23	  int i;
    24	
--> 25	  for(i = 0; i < n; i++) *(x + i) *= 3;
--> 26	  //for(i = 0; i < n; i++) x[i] *= 3;
    27	}
  1. 関数の宣言(1行目と21行目)で int x[] と書いても int *x と書いても同じ
  2. 25行目と26行目も同じこと

演習問題

工事中.HTML版へどうぞ

Q1 初心者

以下の表を埋めましょう.

宣言の仕方変数値(注)アドレスの参照
int型の普通の変数 xint x;x
int型変数へのポインタ変数 y

注: ポインタ変数の場合には,「ポインタ変数の指す先の変数値」という意味として考えましょう

Q2 初級

  1. 以下のその一とその二について,周りの適当な人をつかまえて説明し,納得させましょう.
  2. printf() では普通 & はつけないのに scanf() ではつける理由を説明しなさい.
       int x;
       scanf("%d", &x);
       printf("%d", x);
    

Q3 中級

● 関数とポインタ篇

  1. 以下のプログラムの「ほげほげ」の部分はどう書けばよいか
  2. 完成したプログラムを実行したときの結果

を答えなさい.

#include <stdio.h>

void Hoge(int x, int *y)
{
   ほげほげ: 「xの値」と「yが指す変数値」をかけたものを「yが指す変数値」に代入する.
}

int main()
{
  int a, b;

  for(a = 0; a < 10; a++){
    b = a + 1;
    ほげほげ: 関数Hoge()を呼び出す.引数は,aとbのアドレス.
    printf("%d %d\n", a, b);
  }

   return 0;
}

● 配列とポインタ篇

以下のプログラムを実行したときの結果を答えなさい.

#include <stdio.h>

int main()
{
  int x[10], *p, *q, i;

  for(i = 0; i < 10; i++) x[i] = i;

  p = x; q = &x[1];

  for(i = 0; i < 5; i++){
    printf("%d %d %d\n", i, *p, *(q + 2 * i));
    p = p + 2;
  }

   return 0;
}

Q4 まにあ

以下のプログラムを実行したときの結果を答えなさい.

#include <stdio.h>

void Print3(int *a)
{
   printf("%d %d %d\n", *a, *(a + 1), *a + 5);
}

int main()
{
   int x[10], *p, i;

   for(i = 0; i < 10; i++) x[i] = i;
   p = &x[6];

   Print3(x);
   Print3(&x[5]);
   Print3(++p);

   return 0;
}

トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2022-06-06 (月) 11:04:33