lC言語 C言語の変数とポインタ '96.11. 5  
l.お知らせ 内容 LINK FILE   HTML Win PC Unix MS-DOS C C++ Mfc Java 
.C言語_ 3 フォ-マットコ-ド 6 演算子 9 FOR  集合デ-タ  目的別
1 表示してみる 4 変数と定数 7 条件判断 10 WHIILE  ポインタ     
2 エスケ-プ コ-ド 5 デ-タ入力 8 分岐   未使用  関数   ★ 
変数  実際のポインタの値  ポインタ変数  FAR ポインタ  ポインタのポインタ 
多次元配列とポインタ  用例  ポインタ変数の型変換  関数と変数とポインタ
 

変数名はポインタの別名
変数を使う場合には、次のように宣言してから使います。
int X,Y,Z; char C,S[128],*P;
X,Y,Z は、数値を格納するための変数。(2 Byt)
C は、文字を格納するための変数。  (1 Byt)
S は、文字列を格納するための変数。(1×128 Byt)
P は、文字または文字列用の変数の、ポインタ格納するための変数。(2 Byt)
このように書かれたソースファイルをコンパイル(実行ファイルに変換)すると、次のような処理が行われます。
1.変数を格納するメモリ上の位置を決めます。
2.変数名は全部、そのアドレスであるポインタに変換されます
 計算をしたりその内容を表示する部分に使われている変数名も、全てポインタに変換されます。
3.変数を使った計算式の部分などは、その変数の型に応じた長さ単位で処理するように書換えられます。
 例えば、Z=X; では 2Byt のデータを移動し、
      C=S[0]; では 1Byt のデータを移動します。
※ 変数の型は、1Bytずつ処理するなど、その変数の処理の方法を決めているのです。
アドレス : メモリ上の位置。つまり、コンピュータのノートの行数です。
ポインタ : データなどの位置を指し示すためのアドレスをいいます。
アドレス(address : 住所、口説、応待ぶり) : メモリ上の位置(を表わす番号)
  メモリには、一個所に8ビット(=1バイト) のデータを記録できます。
  各場所は、0から始まる番号を付けて区別します。
  この番号は、1F2A 番地 のように、普通は 16 進数で表わされます。
ポインタ(pointer : 指し示す人、指し示す物) : データなどの位置を示すアドレス

それでも変数は変数!
実行プログラムでは、変数はその格納位置を示すポインタに変換されます。
しかし、変数を処理する部分もポインタに変換されますので、ポインタであることを意識しないでプログラムを書くことができます。
つまり、ソースファイルの中では、X や Y などの変数は完全に変数そのものであると考えることができます。
int X=5;
int Y;
 
Y=X;
   例えば F000 番地に、2 Byt を確保し、数 5 を書込みます。
 例えば F002 番地に、2 Byt が確保されます。
 
 F002 番地に、F000 番地の内容をコピーします。
F000 番地というのは、コンピュータのノートの 61,440行目という意味です。
F000(16進数) = 61,440(10進数)

実際のポインタを知るには
ある変数がメモリ上のどこに格納されているのかを参照するには、変数名の前に & を付けて書きます。
int X;
int Y;
 
Y=&X;
   例えば F000 番地に、2 Byt を確保します。
 例えば F002 番地に、2 Byt が確保されます。
 
 F002 番地に、X の格納位置 F000 を書込みます。

このようなソースコードを書くと正常に動作はしますが、コンパイル時に「変数の型が合わない」というエラーメッセージが出ます。
VisualC では、コンパイルが中断します。
それは、&X も Y も同じ 2Byt の整数値なのですが、プログラムを作る時のミスを防ぐために、ポインタを格納する変数には * を付けて宣言するように決めてあるからです。

ポインタ変数
ポインタを格納する変数はポインタまたはポインタ変数と呼ばれ、ポイントするデータの型と * を付けて宣言します。
ポイントするデータの型を付けて宣言しますが、ポインタ変数自体は 2Byt のデータです。
char X[128];
char* Y;
 
Y=&X[0];
文字を格納するための、配列 X の宣言。(1×128Byt) 
文字用変数のアドレスを格納するための、ポインタ変数を宣言。(2Byt) 
Y でポイントされるデータは、文字として 1Byt づつ処理されます。 
ポインタ変数 Y に、変数 X の先頭のアドレスを代入します。 

* の前後には、スペースを設けても 設けなくてもかまいません。
char* の部分を変数の型であると考えると分かり易いと思います。

ポインタ変数を使ってデータを指定する場合は、ポインタ変数の前に * を付けて書きます。
この場合の * は、ポインタ変数がポイントする中身のデータを取り出す働きをします。

ポインタ変数を宣言するとき : * は、ポインタであることを表わします。
ポインタ変数を使うとき   : * は、ポイント先のデータを取り出します。

char X[128];
char* Y;
 
Y=&X[0];
*Y=*(Y+5);
 文字用の変数配列 X の宣言。(1×128Byt) 
 文字用のデータアドレスを格納するためのポインタ変数を宣言。(2Byt) 
  Y でポイントされるデータは、文字として 1Byt づつ処理されます。 
 ポインタ変数 Y に、変数 X の先頭のアドレスを代入します。 
 ポインタを使って、X[0] に X[5] を代入します。 

文字型変数 X が 100番地から始まっているとすれば、
Y=&X[0]; により、Y には 100が代入されます。
*Y は、100番地の内容という意味です。
*(Y+5) は、100+5 = 105番地の内容という意味です。

この場合は Y が、char *Y と宣言されていますが、
long int *Y として 4Byt のデータ用に宣言されている場合は、
*(Y+5) は、100+5×4 = 120番地から 4Byt のデータという意味になります。
つまり、+5 の部分はアドレスではなく、データの番号です。

FAR ポインタ
普通のパソコンでは、アドレスは 4Byt で表わされます。
C言語ではアドレスを 2Byt で表わします。それはCコンパイラが定めた特定のアドレスを基点にして、そこからの変移で表わすようになっているからです。
大きなデータを扱う場合には、そのポインタを 4Byt にする必要がありますが、その場合には変数型と共に far を付けて宣言します。
char far *Y; 


ポインタのポインタ
変数はポインタ変数を使ってポイントできますが、ポインタ変数もポインタ変数でポイントできます。
char X[128];
char* Y;
char** Z;
 
Y=&X[0];
Z=&Y;
**Z=*(*Z+5);
 文字用の変数配列 X の宣言。(1×128Byt) 
 文字用のデータアドレスを格納するためのポインタ変数を宣言。(2Byt) 
 文字用のデータアドレスを格納するための 
  ポインタ変数の、ポインタ変数を宣言。(2Byt) 
 ポインタ変数 Y に、変数 X の先頭のアドレスを代入します。 
 ポインタ変数 Z に、ポインタ変数 Y のアドレスを代入します。 
 ポインタのポインタを使って、X[0] に X[5] を代入します。 

Y=&anp;X[0] により、Y には X の先頭アドレスが代入されます。*Y は、X[0] の内容です。
    Y は、X の先頭アドレスです。
Z=&Y により、Z には Y のアドレスが格納されます。
    Z は、Y のアドレスです。
   *Z は、Y の内容、つまり X の先頭アドレスです。
   *Z+5は、X の5番目のデータのアドレスです。
  *(*Z+5)は、X の5番目のデータのアドレスの内容(5番目のデータ)です。

なお、&X[0] は X[0] のアドレスですが、X も X[0] のアドレスを指します。
つまり、&X[0] と X は同じ意味を持ちます。


多次元配列とポインタ
多次元配列とそのポインタの関係を、例で示します。
char s[I][J][K];
char *p,*pp;
 
p=&s[i][j][k];
pp=s[i][j];
3次元配列 S を宣言
ポインタ p と pp を宣言
 
ポインタ p に s[i][j][k]のアドレスを代入
ポインタ pp に s[i][j][0]のアドレスを代入
この場合、次のように考えます。

 &s[i][j][k] は、s[i][j][k] へのポインタ。
  s[i][j][k] は、s[i][j][k] の内容そのもの。
  s[i][j]    は、s[i][j][0] へのポインタ。        *s[i][j] は、s[i][j][0] の内容。
  s[i]       は、s[i][0][0] へのポインタのポインタ。   *s[i]  は、s[i][0][0] へのポインタ。
                                        **s[i]  は、s[i][0][0] の内容。
  s は、s[0][0][0] へのポインタのポインタのポインタ。  *s は、s[0][0][0] へのポインタのポインタ。
                                 **s は、s[0][0][0] へのポインタ。
                                 ***s は、s[0][0][0] の内容。
従って、s[i][j][k] は次のように表わすこともできます。
     *(s[i][j]+k)
   *(*(s[i]+j)+k)
 *(*(*(s+i)+j)+k) または *(pp+k) .....pp=s[i][j]  pp=*(*(s+i)+j)
   *(pp+k)
ただし、意味がないポインタなどは無視されるか、意味があるように修正されます。
 &s[i][j][k] や s[i][j] &s[i][j][0] は、char *p に代入できます。   s[i] 早@&(&(s[i][0][0]))      は、char **p に代入できません。   s 早@&(&(&(s[0][0][0])))      は、char ***p に代入できません。 &(s[i][j][k])はデータのアドレスですが、更にそのアドレスというのは意味がありません。 ***s は、s[0][0][0] の内容です。 **s は、s[0][0][0] へのポインタです。 *s は、s[0][0][0] へのポインタのポインタで実体がないので、**s として処理されます。 ただし前の項目や次の項目のように、先頭アドレスをポインタに代入したような場合には、 ポインタのポインタや、ポインタのポインタのポインタにも意味があります。

変数配列とポインタの具体例
#include<stdio.h>	/* 表示関数などが入ったファイルをこの位置に挿入 */
char s[3][4]={"ABC","DEF","G"};	/* 文字配列の宣言と、内容の初期化 */
char *p[3],**pp;		/* ポインタ配列と、ポインタのポインタを宣言 */
int i;				/* 表示のための繰返し回数用に変数を宣言 */
void main()			/* このプログラムの中心部分 {}の間 */
{
  printf("文字配列の内容を表示します。\n");
  printf("文字表示=");for(i=0;i<3;i++)printf("%s ",s[i]);printf("\n");
  printf("16進表示=");for(i=0;i<12;i++)printf("%x ",s[0][i]);printf("\n");
  printf("ポインタ=");for(i=0;i<12;i++)printf("%x ",&s[0][i]);printf("\n");

  p[0]=&s[0][0];	/* p[0]=s[0] でも可。ポインタ配列へアドレスを代入。 */
  p[1]=&s[1][0];
  p[2]=s[2];
  pp=&p[0];		/* pp=p でも可。ポインタのポインタにアドレスを代入 */
  s[1][0]=s[0][0];	/* D の位置に A をコピー。変数配列を直接に指定。 */
  *(p[1]+1)=*(p[0]+1);	/* E の位置に B をコピー。ポインタを使用。 */
  *(*pp+6)=*(*pp+2);	/* F の位置に C をコピー。ポインタのポインタを使用。*/

  printf("\n変更された内容を表示します。\n");
  printf("文字表示=");for(i=0;i<3;i++)printf("%s ",s[i]);printf("\n");
  printf("16進表示=");for(i=0;i<12;i++)printf("%x ",s[0][i]);printf("\n");
  printf("ポインタ=");for(i=0;i<12;i++)printf("%x ",&s[0][i]);printf("\n");
  printf("\nポインタ配列の内容を表示します。\n");
  printf("16進表示=");for(i=0;i<3;i++)printf("%x ",p[i]);printf("\n");
}


ポインタ変数の型変換
int X[100]; のように変数 X を宣言すると、2Byt のデータが 100個格納できる変数配列が作られます。
X[50]=5; とプログラムすると、変数配列 X の 50番目のデータ(2Byt)に数値 5 が代入されます。
int *Y; としてポインタを宣言し、
Y=&X[0]; としてアドレスを代入して、
*(Y+50)=5 とプログラミングしても、同様に変数配列 X の 50番目のデータに 2Byt の数値が代入されます。
それは、int という型が 2Byt のデータを表わすための型だからです。

しかし、int X[100]; と宣言した配列でも、次のようにすれば別な型のデータを代入することができます。
int X[100];
int *Y;
long Z=0x12345678;
 
Y=&X[0];
*(long *)(Y+50)=Z;
Z=*(long *)(Y+50);
 int型の変数配列 X を宣言。(2×100Byt)
 int型データ用のポインタ Y を宣言。(2Byt)
 long型の変数 Z を宣言/初期化。(4Byt)
 
 ポインタ変数 Y に、変数 X の先頭のアドレスを代入。
 X[50]〜X[51] に、Z を代入します。
 Z に、X[50]〜X[51] を代入します。

Y には、X の先頭アドレスが代入されます。Y は X のポインタになります。
Y+50 は、X の 50番目のデータ X[50] のポインタです。
*(Y+50) は、X[50] から 2Byt の int型のデータです。
(long *)(Y+50) は、X[50] のポインタを int型から long型に変換したものです。
*(long *)(Y+50) は、X[50] から 4Byt の long型のデータです。

備考 : char型のデータは 1Bytですから、2桁の 16進数が格納できます。
 int や long型など 1Byt 以上のデータは、メモリ上には 1Byt単位で逆順に格納されます。
 例えば、16進数の 12345678 は、78563412 のように格納されます。
 ただし、格納したときと同じ型で読み出す場合には、元の通りに復元されます。

備考 : ポインタを使えば上記のように型の異なるデータを格納できるほか、宣言した変数配列の範囲を超えて読み書きすることができます。
 上の例では X は 100個分のデータ用の配列を宣言していますが、*(Y+200)=5 のように範囲を超えて代入ができます。
 また、変数を宣言しなくてポインタだけを宣言した場合にも、そのポインタを使ってデータを代入できます。
 このような間違った代入をした場合は、そこにあった元のデータが壊されることになるので、ほとんどの場合プログラムは暴走してしまいます。

  


関数と変数とポインタ
引き数を持つ関数は、引数名と同じ名前の変数を作り、与えられたデータをその変数にコピーします。
引数を用いる理由は、引数を変えるだけでひとつの関数が色々なデータを処理できるからです。
しかし、大きなデータの場合に変数を渡すためには引数の数がとても多くなって関数を作った意味がなくなりますから、普通はポインタを渡します。
例を示します。
#include<stdio.h>		/* stdio.h をここに挿入 */
int X[5]={1,2,3,4,5};           /* 変数配列の宣言と初期化 */
int Y[5]={6,7,8,9,10};
int Z;

void func1()                    /* 返り値も引数も持たない関数 */
{ int i;
  Z=0;for(i=0;i<5;i++)Z=Z+X[i]; /* X の合計を計算します。 */
}
                                /* データを直接受取る関数 */
void func2(int X0,int X1,int X2,int X3,int X4)
{ Z=X0+X1+X2+X3+X4;}            /* X0〜X4 の合計を計算します。 */

int func3(int *Q)               /* ポインタを受取り、処理結果を返す関数 */
{ int i,z=0;
  for(i=0;i<5;i++)z=z+*(Q+i);   /* *(Q+0)〜*(Q+4) の合計を計算します */
  return z;
}

void main()
{ int i;
  printf("変数配列 X=");
  for(i=0;i<5;i++)printf("%d ",X[i]);printf("\n");
  printf("変数配列 Y=");
  for(i=0;i<5;i++)printf("%d ",Y[i]);printf("\n\n");

  func1();     /* 返り値も引数も持たない関数 */
  printf("func1 の結果 Z=%d\n",Z);

               /* データを直接受取る関数 */
  func2(X[0],X[1],X[2],X[3],X[4]);
  printf("func2 の結果 Z=%d\n",Z);

  Z=func3(X);  /* ポインタを受取り、処理結果を返す関数 */
  printf("func3 の結果 Z=%d\n",Z);
  Z=func3(Y);
  printf("func3 の結果 Z=%d\n",Z);
}
func1 は、変数配列を直接使います。
変数を直接使っているので、別な変数 int Y[5] などを処理することはできません。

func2 は、X[0]〜X[4]を、自分で作った変数 X0〜X4 にコピーしてから使います。
処理結果は呼び出し元の変数 Z に直接代入しています。
引数に Y[0]〜Y[4]を与えるなど、別な変数も処理することができます。
※ コピーした変数 X0〜X4 を操作するので、X0=X0+1 などとしてその変数を変化させても、元の変数 X[0]〜X[4]は変化しません。

func3 は、配列のポインタを、自分で作ったポインタ変数 Q にコピーして使います。
なお、変数配列 int X[5] の変数名 X は、その配列の先頭ポインタです( =&X[0] )。
処理結果 z は返り値として持ち帰り、親の変数 Z に代入しています。
引数に別な配列のポインタを与えれば、その配列を処理できます。
※ コピーしたポインタ変数 Q を操作するので、Q=Q+1 のように変化させても、コピー元のポインタ X は影響を受けません。

T


C言語  集合デ-タ  ポインタ  関数
 
  mtoga@sannet.ne.jp   登録日 '96. 6.15
URL : http://www.page.sannet.ne.jp/mtoga/index.html