変数を用いる場合には, メモリー(記憶域)上に必要な領域が確保され, そこに変数の値が保存される. 「値」は,そこに保存されている内容であり, 「アドレス」は, 保存されている場所である. 「ポインタ変数」はアドレスを格納したり, アドレスに関する演算を行なうのに適した機能を有する変数である. 表3に通常の変数とポインター変数を並べて示す.
// アドレス演算子 &, 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;
}
以下に, アドレスを仮引数とする関数呼び出しの例を示す.
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;
}
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;
}
#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;
}
%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;
}
&を付けることにより得られる.
%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;
}
%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;
}
プログラミングにおいては, 記憶域上でどこに何が記憶されているかを理解することが重要である. 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このことから, 記憶域が次のように利用されていることが分かる.
/*
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.5]および 「記憶域上のイメージ」[10.6]を参照のこと.
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;
}
プログラム 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