Subsections

10章 ポインタ (H.24年12月19日)

10.1 新しい文法事項

アドレス演算子 (単項&演算子)
&a: aのアドレスを得る. (Table 10-1)

間接演算子 (単項*演算子)
*a: a が指すオブジェクト. aに代入されているアドレスの 場所にある値を参照したり, 代入したりする.

ポインター変数と通常の変数の比較

変数を用いる場合には, メモリー(記憶域)上に必要な領域が確保され, そこに変数の値が保存される. 「値」は,そこに保存されている内容であり, 「アドレス」は, 保存されている場所である. 「ポインタ変数」はアドレスを格納したり, アドレスに関する演算を行なうのに適した機能を有する変数である. 表3に通常の変数とポインター変数を並べて示す.


Table 3: 通常の変数とポインター変数
  変数 ポインタ−変数
宣言 int a; int *p;
a *p;
アドレス &a p


10.2 値, アドレス, サイズ

Program 1: アドレス と サイズ
// アドレス演算子 &, sozeof 演算子により記憶域上の領域を調べられる.
//  変数の前にアドレス演算子 & をつけると, その変数のアドレスを得られる.

#include <stdio.h>

int main(void)
{
  int a = 10;
  double x = 10.0;
  char c = '1';

  // アドレスは 書式に %p を指定して出力する.
  printf("a: %p\n", &a); // a のアドレスを出力する.
  printf("x: %p\n", &x); // x のアドレスを出力する.
  printf("c: %p\n", &c); // c のアドレスを出力する.

  printf("a: %u\n", sizeof(a)); // a のサイズ
  printf("x: %u\n", sizeof(x)); // x のサイズ
  printf("c: %u\n", sizeof(c)); // c のサイズ

  return 0;
}

Program 2: ポインタ変数

// ポインタ変数: アドレスを保持, 操作するための変数
// 変数名の前に * を付けて宣言する.

#include <stdio.h>

int main(void)
{
  int a;  // 通常の変数
  int *p; // ポインタ変数

  p = &a; // a のアドレスを p に代入

  printf("&a = %p\n", &a); // a のアドレスを表示
  printf("p  = %p\n",  p); // p の値を表示

  return 0;
}

Program 3: ポインタ変数の値,アドレス,サイズ

// ポインタ変数: アドレスを保持, 操作するための変数
// 変数名の前に * を付けて宣言する.

#include <stdio.h>

int main(void)
{
  int a;  // 通常の変数
  int *p; // ポインタ変数

  p = &a; // a のアドレスを p に代入

  printf("a = %d\n", a);                  // a の値
  printf("&a = %p\n", &a);                // a のアドレス
  printf("sizeof(a) = %u\n", sizeof(a) ); // a のサイズ

  printf("p = %p\n", p);                  // p の値
  printf("&p = %p\n", &p);                // p のアドレス
  printf("sizeof(p) = %u\n", sizeof(p) ); // p のサイズ

  return 0;
}

Program 4: 間接演算子 *

//
// ここが一番重要です.
//
// 間接演算子 * : アドレスで指定される領域の値を参照したり, 代入したりする.

#include <stdio.h>

int main(void)
{
  double x;
  double *p; // 操作しようとする変数と同じ型で宣言する.

  x = 10.0;
  p = &x;

  printf("(1)  x = %f\n", x);
  printf("(1) *p = %f\n", *p); // p が保持するアドレス(ここではxのアドレス)の領域の値を参照.

  *p = 20.0; // p が保持するアドレス(ここではxのアドレス)の領域に代入.

  printf("(2)  x = %f\n", x);
  printf("(2) *p = %f\n", *p);

  return 0;
}

Program 5: 間接演算子 * を用いたときの値, アドレス, サイズ

// 間接演算子 * : アドレスで指定される領域の値を参照したり, 代入したりする.

#include <stdio.h>

int main(void)
{
  double x;
  double *p;

  x = 10.0;
  p = &x;

  printf("(1)        x  = %f\n", x);
  printf("(1)       &x  = %p\n", &x);
  printf("(1) sizeof(x) = %u\n", sizeof(x));

  printf("(1)        p  = %p\n", p);
  printf("(1)       &p  = %p\n", &p);
  printf("(1) sizeof(p) = %u\n", sizeof(p));
  printf("(1)       *p  = %f\n", *p);

  *p = 20.0;

  printf("(2)        x  = %f\n", x);
  printf("(2)       &x  = %p\n", &x);
  printf("(2) sizeof(x) = %u\n", sizeof(x));

  printf("(2)        p  = %p\n", p);
  printf("(2)       &p  = %p\n", &p);
  printf("(2) sizeof(p) = %u\n", sizeof(p));
  printf("(2)       *p  = %f\n", *p);

  return 0;
}


10.3 アドレスを仮引数とする関数呼び出し

アドレスを仮引数とする関数呼び出しでは, 呼び出し側の変数の値が変わる. 従って, 変数の遮蔽が行えないことに注意する必要がある.

以下に, アドレスを仮引数とする関数呼び出しの例を示す.

Program 6: 関数の引数としてのポインタ変数

// 関数の引数としてのポインタ変数

#include <stdio.h>

void func(double *x)
{
  *x = 20.0;
}

int main(void)
{
  double a = 10.0;

  printf("(1) a = %f\n", a);
  func(&a); // x = &a の代入がおきる. & を付けてアドレスを渡している点に注意.
  printf("(2) a = %f\n", a);
  
  return 0;
}

Program 7: アドレスを仮引数とする関数呼び出しの場合について 仮引数のアドレス, 値, サイズを調べてみる.

// ポインタ変数を引数とする関数

#include <stdio.h>

void func(double *x)
{
	 printf("(2) &x = %p\n", &x); // x のアドレス.
	 printf("(2) sizeof(x) = %u\n", sizeof(x)); // x のアドレス.
	 printf("(2)  x = %p\n",  x); // xの値 (ここでは a のアドレス)
	 printf("(2) *x = %f\n", *x); // *x の値.

	 *x = 20.0;

	 printf("(2) *x = %f\n", *x); // *x の値.
}

int main(void)
{
	 double a = 3.0;

	 printf("(1) a = %f\n",  a); // a の値
	 printf("(1) &a = %p\n", &a); // a のアドレス
	 printf("(1) sizeof(a) = %u\n", sizeof(a)); // a のサイズ

	 func(&a); // funcの引数のxに aのアドレス&a が代入される

	 printf("(3) a = %f\n",  a);

	 return 0;
}

  1. main関数内で, 通常の変数 int a;を宣言している. この変数の値はaアドレスは&aで得られる. printfの書式には, アドレスを出力するときは %pを用いる.
  2. func(&a);により関数funcを呼び出している. ここで, 変数名aの前に&を付けることにより, 関数funcに変数aのアドレスを渡している.
  3. 関数funcは, 仮引数としてint *xを受け取っている. ここでxは, アドレスを受け取るポインタ変数である. ここでは, main関数中の変数aのアドレスを受け取り, xaのアドレスが代入される.
  4. ポインタ変数xの前に*を付けることで, xが指すアドレスに格納されている値を参照したり, 代入したりすることができる. *xが通常の変数に相当する. ここでは, xは, main関数中のaを指しているので aに代入が起き, main関数中のaの内容が書き変わる.

Program 8: 値を渡すときと比較しましょう.

// ポインタ変数を引数とする関数

#include <stdio.h>

// x は func内で寿命を持つ局所変数.
void func(double x)
{
	 printf("(2) x = %f\n", x); // x の値.
	 printf("(2)  x = %p\n",  &x); // xのアドレス (main中の a のアドレスとは異なる.)
	 printf("(2) sizeof(x) = %u\n", sizeof(x)); // x のサイズ

	 x = 20.0;

	 printf("(2) x = %f\n", x); // x の値.
}

int main(void)
{
	 double a = 3.0;

	 printf("(1) a = %f\n",  a); // a の値
	 printf("(1) &a = %p\n", &a); // a のアドレス
	 printf("(1) sizeof(a) = %u\n", sizeof(a)); // a のサイズ

	 func(a); // x=a の代入がおきる. (funcの引数のxに aの値が代入される)

	 printf("(3) a = %f\n",  a);

	 return 0;
}


10.4 ポインタは値を代入してから用いる.

ポインタは値を代入してから用いる必要がある. このことを理解するために次のプログラムを考える. このプログラムでは, 変数aのアドレスをptrに代入し, ptrが指すアドレス上にある値を出力している. 結果的に a の値を出力していることになる.
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 int a = 10;
	 int *ptr;

	 // printf(" *ptr=%d\n", *ptr); // 誤り
	 ptr = &a; // 必ずアドレスを代入してから用いる.
	 printf(" *ptr=%d\n", *ptr);      // 正しい

	 return EXIT_SUCCESS;
}

これまでに用いてきた通常の変数も値を代入してから用いる必要がある. 次のプログラムでは, a=10を代入した後 a の値を表示している. しかし, 代入する前に 表示するのは誤り.

ただし, static をつけずに宣言されたオブジェクト(変数)である 自動変数は, 宣言部分を実行する時に, 記憶域上に領域が確保される. このとき同時にアドレスも確定する.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 int a;   // ここでは a の値は不定. a のアドレス &a は確定している.
	 // static を付けずに宣言されたオブジェクト(変数) である自動変数は,
	 // 宣言部分を実行するときに, 記憶域上に領域が確保される.
	 // この時アドレスも確定する.
	 
	 printf("[1] &a = %p\n", &a);
	 // printf("[2] a=%d\n", a); // まだ a の値は不定なので誤り.
	 
	 a = 10; // a  の値は 10

	 printf("[3] &a = %p\n", &a);
	 printf("[4] a=%d\n", a); // a の値は確定しているので正しい.

	 return EXIT_SUCCESS;
}

同様に, ポインタも値を代入してから用いる必要がある. 次の例では, aのアドレスを代入した後, ptrの値を用いている. ptr = &aより前では, ptrの値は不定であるので, ptr, *ptrの値を用いることはできない.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 int a;
	 int *ptr;   // ここでは ptr の値は不定. ptr のアドレス &ptr は確定している.
	 
	 printf("[1] &ptr = %p\n", &ptr);
	 // printf("[2] ptr=%p\n", ptr);   // まだ ptr の値は不定なので誤り.
	 // printf("[3] *ptr=%d\n", *ptr); // まだ ptr の値は不定なので誤り.
	 
	 a = 10; // a  の値は 10
	 ptr = &a; // ptr の値は &a (a のアドレス.)

	 printf("[4] &ptr = %p\n", &ptr);
	 printf("[5] ptr=%p\n", ptr);   // ptr の値は確定しているので正しい.
	 printf("[6] *ptr=%d\n", *ptr); // ptr の値は確定しているので正しい.

	 return EXIT_SUCCESS;
}

次のように宣言時に初期化することも可能.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 int a; // ここで a のアドレスが確定する.
	 int *ptr = &a;   // ポインタ変数の初期化.
	 
	 a = 10;

	 printf("[1] &ptr = %p\n", &ptr);
	 printf("[2] ptr=%p\n", ptr);
	 printf("[3] *ptr=%d\n", *ptr);

	 return EXIT_SUCCESS;
}

ポインタptrに値(アドレス)を代入してあっても, ポインタが指す変数aの値が不定である場合は, *ptr も同様に不定である. ポインタの指すアドレス上の値を参照するときは, あらかじめその値を確定しておく必要がある.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 int a; // a の値は不定. a のアドレスは確定.
	 int *ptr = &a;
	 
	 printf("[1] &ptr = %p\n", &ptr);
	 printf("[2] ptr=%p\n", ptr);
	 // printf("[3] *ptr=%d\n", *ptr); // まだ ptr が指すアドレス上の値(aの値)が不定なので誤り.

	 a = 10; // a  の値は 10

	 printf("[4] &ptr = %p\n", &ptr);
	 printf("[5] ptr=%p\n", ptr);
	 printf("[6] *ptr=%d\n", *ptr); // ptr の指すアドレス上の値が確定しているので正しい.

	 return EXIT_SUCCESS;
}

10.5 メモリ上の配置を調べる.

  1. sizeof によりメモリ上のサイズを得られる. サイズは 符号なしの整数であるので, printf 関数に与える書式として %uを用いる.
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    	 char c = 'a';
    	 char strA[]   = "ABCDEFG";
    	 char strB[10] = "ABCDEFG";
    
    	 printf("%u\n", sizeof(c));      // c の大きさ.
    	 printf("%u\n", sizeof(strA));    // strA の大きさ.
    	 printf("%u\n", sizeof(strB));    // strB の大きさ.
    	 printf("%u\n", sizeof(strA[0])); // strA[0] の大きさ.
    	 printf("%u\n", sizeof(strA[1])); // strB[1] の大きさ.
    
    	 return EXIT_SUCCESS;
    }
    
  2. 配列でない変数のアドレスは, &を付けることにより得られる.
  3. アドレスをprintfで表示するときは, 出力書式に%p を用いる. このときアドレスは, 16進数で表現される.
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    	 char strA[] = "ABCDEFG";
    	 char c = 'a';
    	 char strB[10];
    
    	 printf("%p\n", strA);     // strAの先頭の場所.
    	 printf("%p\n", &c);       // 配列でないときは, & を付ける.
    	 printf("%p\n", &(strA[2])); // strA[2] の場所は &( strA[2] )
    	 printf("%p\n", strB);     // 初期化しなくても場所の確保が行われる.
    
    	 return EXIT_SUCCESS;
    }
    

  4. 10進数でアドレスを表示したい場合はキャストして, %uを用いて出力する.
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    	 int a = 3;
    
    	 printf("%p\n", &a);          // アドレスを16進数で表示.
    	 printf("%u\n", (unsigned)&a);  // アドレスを10進数で表示.
    	 return EXIT_SUCCESS;
    }
    

10.6 記憶域上のイメージ

プログラミングにおいては, 記憶域上でどこに何が記憶されているかを理解することが重要である. C言語の変数の, アドレス, サイズ, 値は, 次のようにして調べられる.

#include <stdio.h>
#include <stdlib.h>

// ポインタ変数のサイズは, メモリ上のどのアドレスも表すことができるよう
// コンパイラが選択する.

int main(void)
{
	 double x;
	 double *ptr;

	 x = 10.0;
	 ptr = &x;
	 
	 printf("sizeof(x)=%u\n", sizeof(x));      // x の大きさ(長さ).
	 printf("sizeof(ptr)=%u\n", sizeof(ptr));  // ptr の大きさ(長さ).

	 printf("&x = %p\n", &x); // x の先頭アドレス(場所).
	 printf("&ptr = %p\n", &ptr); // ptr の先頭アドレス(場所).

	 printf("x=%e\n", x); // x の値.
	 printf("ptr=%p\n", ptr); // ptr の値.

	 // ここで ptr の値が x の先頭アドレス一致することに注意.
	 
	 return EXIT_SUCCESS;
}

このプログラムを実行すると例えば, 次のような結果になる.

sizeof(x)=8
sizeof(ptr)=4
&x = 0xbfc022a8
&ptr = 0xbfc022a4
x=1.000000e+01
ptr=0xbfc022a8
このことから, 記憶域が次のように利用されていることが分かる. \includegraphics{src/pointer/pointer_memory.eps}

10.7 ポインターが有用な例

/*
 a が x と y の範囲に入っていることを判定するプログラム.
 必ずしも x < y ではないことに注意.
*/

#include <stdio.h>

int main(void)
{
    double x, y, tmp, a;
    x = 4.0;
    y = 2.0;

    if(x>y){ // x < y となるように  x, y を整理する.
	tmp = y;
	y = x;
	x = tmp;
    }

    a = 10.0;

    if(x<a && a<y)
	printf("a は x から y の間の実数です.\n");
    else
	printf("a は x から y の間の実数ではありません.\n");

    return 0;
}
/*
 a が x と y の範囲に入っていることを判定するプログラム.
 必ずしも x < y ではないことに注意.
*/

#include <stdio.h>

// *p < *q となるよう並び替える関数.
void sort_values(double *p, double *q)
{
    double tmp;

    if ( *p > *q ) {
	tmp = *p;
	*p = *q;
	*q = tmp;
    }
}

int main(void)
{
    double x, y, a;
    x = 4.0;
    y = 2.0;

    sort_values(&x, &y);

    a = 10.0;

    if(x<a && a<y)
	printf("a は x から y の間の実数です.\n");
    else
	printf("a は x から y の間の実数ではありません.\n");

    return 0;
}
#include <stdio.h>

// 2つの数を入れ替える.
void swap(int *s, int *t)
{
    int tmp;

    tmp = *s;
    *s = *t;
    *t = tmp;
}

// *p, *q, *r の順に並べ変える.
void sort_values(int *p, int *q, int *r)
{
    if ( *p > *q)
	swap(p, q);

    if ( *p > *r)
	swap(p, r);

    if ( *q > *r)
	swap(q, r);
}


// 3つ変数それぞれの絶対値を求める.
void absolute_values(int *p, int *q, int *r)
{
    if(*p<0.0)
	*p = - *p;
    if(*q<0.0)
	*q = - *q;
    if(*r<0.0)
	*r = - *r;
}


int main(void)
{
    int a, b, c;

    a = 2;
    b = 3;
    c = 5;

    // 入力された値を以下の処理で扱い易いように加工する.
    absolute_values(&a, &b, &c); // 負の数の時は正の数にする.
    sort_values(&a, &b, &c); // 小さい順に並べ変える.

    if(a*a + b*b == c*c)
	printf("直角三角形です.\n");
    else
	printf("直角三角形ではありません.\n");

    return 0;
}

10.8 自由課題課題

  1. [自由課題1] アドレス演算子および間接演算子を用いたC言語のプログラムを作成せよ.
    1. 教科書 10-1 ポインタ参照のこと.
    2. 「ポインター変数と通常の変数の比較」10.1 および 通常の変数の値とアドレス[10.3]参照のこと.
    3. 間違えやすいので「ポインタは値を代入してから用いる」 [10.4]を十分理解すること.

  2. [自由課題2] 関数の仮引数としてポインタ変数を用いたC言語のプログラムを作成せよ.
    1. 教科書 10-2 ポインタと関数参照のこと.
    2. C言語では, 関数は1つの値を返すことしかできない. 多くの値を関数で求めたいときには, 仮引数をポインタ変数にするか構造体を用いる.

  3. [自由課題3] int 型, double 型のポインタ変数および通常の変数について(a)サイズ, (b)先頭のアドレス, (c)値を調べよ. 関数を呼び出す場合, main関数の中でのみ使用する場合についてそれぞれ調べること. これにより記憶域上の配置と記憶域の中身(値)を図で表せ.

    「メモリ上の配置を調べる」[10.5]および 「記憶域上のイメージ」[10.6]を参照のこと.

10.9 プログラム

10.9.1 配列

Program A 配列のアドレス と サイズ

// 配列の記憶域上でのイメージ

#include <stdio.h>

int main(void)
{
  int a[3] = {10, 20, 30};

  // 配列名aは配列aの先頭のアドレスを与える.

  printf("a   : %p\n", a   ); // a のアドレス (=a[0]のアドレス) を出力する.
  printf("a[0]: %p\n", a[0]); // a[0] のアドレス
  printf("a[1]: %p\n", a[1]); // a[1] のアドレス
  printf("a[2]: %p\n", a[2]); // a[2] のアドレス


  printf("a   : %u\n", sizeof( a    ) ); // a のサイズ
  printf("a[0]: %u\n", sizeof( a[0] ) ); // a[0] のサイズ
  printf("a[1]: %u\n", sizeof( a[1] ) ); // a[0] のサイズ
  printf("a[2]: %u\n", sizeof( a[2] ) ); // a[0] のサイズ

  // 1つの配列の各要素は, 連続した領域に確保される.

  return 0;
}

Program B

// int 型配列 int a[5] の (a)値, (b)アドレス, (c)サイズを求めるプログラム


#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int a[5] = {1, 2, 3, 4, 5};
	int i;

	for(i=0; i<5; i++){
		printf("\n--- a[%d] ---\n", i);
		printf("value: %d\n", a[i]); // a[i] の値.
		printf("address: %p\n", &a[i]); // a[i] のアドレス.
		printf("size: %d\n", sizeof(a[0])); // a[0] のサイズ.
	}

	return EXIT_SUCCESS;
}

10.9.2 複数の引数

プログラム C

/*
  main 関数中の変数 int a, double b および func 関数の引数 int c, double d の
  (a)値, (b)アドレス, (c)サイズを求めるプログラム
*/

#include <stdio.h>
#include <stdlib.h>

void func(int c, double d)
{
	printf("\n=== in func ===\n");
	printf("--- c ---\n");
	printf("value: %d\n", c); // c の値.
	printf("address: %p\n", &c); // c のアドレス.
	printf("size: %d\n", sizeof(c)); // c のサイズ.
	printf("--- d ---\n");
	printf("value: %f\n", d); // d の値.
	printf("address: %p\n", &d); // d のアドレス.
	printf("size: %d\n", sizeof(d)); // d のサイズ.
}

int main(void)
{
	int a = 3;
	double b = 4.0;
	
	printf("\n=== in main ===\n");
	printf("--- a ---\n");
	printf("value: %d\n", a); // a の値.
	printf("address: %p\n", &a); // a のアドレス.
	printf("size: %d\n", sizeof(a)); // a のサイズ.
	printf("--- b ---\n");
	printf("value: %f\n", b); // b の値.
	printf("address: %p\n", &b); // b のアドレス.
	printf("size: %d\n", sizeof(b)); // b のサイズ.

	func(a, b);
	
	return EXIT_SUCCESS;
}

プログラム D

/*
  main 関数中の int a, double b
  および  func の引数の引数int *c, double *d
  の (a)値, (b)アドレス, (c)サイズを求めるプログラムである.

*/

#include <stdio.h>
#include <stdlib.h>

void func(int *c, double *d)
{
	printf("\n=== in func ===\n");

	printf("--- c ---\n");
	printf("value: %d\n", *c); // cに代入されたアドレスに保持されている値.
	printf("value: %p\n", c); // c の値. (=aのアドレス)
	printf("address: %p\n", &c); // c のアドレス.
	printf("size: %d\n", sizeof(c)); // c のサイズ.

	printf("--- d ---\n");
	printf("value: %f\n", *d); // d に代入されたアドレスに保持されている値.
	printf("value: %p\n", d); // d の値. (=bのアドレス)
	printf("address: %p\n", &d); // d のアドレス.
	printf("size: %d\n", sizeof(d)); // d のサイズ.

	*c = 10;
	*d = 20.0;
}

int main(void)
{
	int a = 3;
	double b = 40.0;
	
	printf("\n=== in main 1 ===\n");
	printf("--- a ---\n");
	printf("value: %d\n", a); // a の値.
	printf("address: %p\n", &a); // a のアドレス.
	printf("size: %d\n", sizeof(a)); // a のサイズ.
	printf("--- b ---\n");
	printf("value: %f\n", b); // b の値.
	printf("address: %p\n", &b); // b のアドレス.
	printf("size: %d\n", sizeof(b)); // b のサイズ.

	func(&a, &b);
	
	printf("\n=== in main 2 ===\n");
	printf("--- a ---\n");
	printf("value: %d\n", a); // a の値.
	printf("address: %p\n", &a); // a のアドレス.
	printf("size: %d\n", sizeof(a)); // a のサイズ.
	printf("--- b ---\n");
	printf("value: %d\n", a); // b の値.
	printf("address: %p\n", &a); // b のアドレス.
	printf("size: %d\n", sizeof(a)); // b のサイズ.

	return EXIT_SUCCESS;
}

(c)1999-2013 Tetsuya Makimura