これはrst2hooktailの記事ソース保存・変換用です(詳細).
============================================================ 自分で調べるCのポインタ ============================================================ C言語の開発環境があれば,自分でテストプログラムを作ることにより, 手持ちの教科書・参考書だけでポインタの性質を理解することができます. 以下に調べ方の例を示します.例を変更して自分でいろいろ試みてください. 本文に誤りがあったとき自信を持って訂正できます. 簡単な例 ============================================================ まず次の例を考えましょう.結果が予想できない人は実際にプログラムを 作って実行してください. 例1.次のプログラムの出力は「1, 4」. <tex> & #include <stdio.h>\\ & int main(void)\\ & {\\ & & int n[3]={4, 5, 6}, *p=n;\\ & & printf("%d, %d\n", p==\&n[0], *p);\\ & & return 0;\\ & } </tex> 問1.例1の n, p について 1. p[1] の値を示せ. 2. &n[1]-&n[0] の値を示せ. 例1で実在する変数は n[0], n[1], n[2], p のみですが,「p=n;」によって n[i] の代わりに p[i] を使うことができます.一般に <tex> p[i]==*(p+i), \&p[i]==p+i </tex> したがって p==\&p[0], *p==p[0] であり,「p=n+1;」とすると <tex> p[-1]==n[0], p[0]==n[1], p[1]==n[2] </tex> となります.Cで「&p[i]==p+i」と定めたのは「p+=sizeof(int);」の代わりに 「p++;」を使いたいためであると思われます.『アドレス+整数』と 『アドレス−アドレス』は次元(?)が違うので &n[1]-&n[0]==sizeof(int) と 定めることもできますが [*]_ ,&n[1]!=&n[0]+(&n[1]-&n[0]) となるので,Cの 言語仕様はそうなっていません.実際 <tex> & #include <stdio.h>\\ & int main(void)\\ & {\\ & & int n[3], k0=\&n[0], k1=\&n[1];\\ & & printf("%d, %d\n", k1-k0, \&n[1]-\&n[0]);\\ & & return 0;\\ & } </tex> を実行すると,k1-k0==sizeof(int), &n[1]-&n[0]==1,2*(&n[1]-&n[0]==2 と なります.「k0=&n[0];」は警告が出るかもしれませんが,無意味な代入では ないのでエラーにはなりません. .. [*] Cの表現における &n[1]==n+1 && &n[1][2]!=(n+1)+2 等の不自然な式が なくなります.どう定めても複雑な式には歪が伴います. 2次元配列とポインタの配列 ============================================================ プログラム例を示す前に <tex> int n[2][2], *p[2], (*q)[][2]; </tex> で宣言される n, p, q について説明します.Cの記法ではありませんが <tex> 「int & n[2][2];」&「int & *p[2];」&「int & (*q)[][2];」\\ 『int[2] & n[2]; 』&『int* & p[2]; 』&『int[2][] & *q; 』\\ 『int[2][2] & n; 』&『int*[2] & p; 』&『int[2][]* & q; 』\\ </tex> と書き換えることにより,n は整数の2次元配列,p は整数へのポインタの 1次元配列,q は整数の2次元配列へのポインタであることが分かりやすく なります.なお,「int m[2][3][4];」と宣言されているとき, <tex> \&m[i][j][k]-\&m[0][0][0]==4*(3*i+j)+k </tex> であることは <tex> & for(i=0; i<2; i++)for(j=0; j<3; j++)for(k=0; k<4; k++){ & & printf("%d,", \&m[i][j][k]-\&m[0][0][0]); & } </tex> を実行することで確認できます.また &m[i][j][k] に対して <tex> m[i][j]+k==\&m[i][j][k], m[i]==m[i][0], m==m[0] </tex> と定められています.ただし,k が 0 でないとき m[i][0] に m[i] を代入 できません.m[0] と m についても同様です. 例2.次のプログラムの出力は「5, 7」. <tex> & #include <stdio.h>\\ & int main(void)\\ & {\\ & & int n[2][2]={{4, 5}, {6, 7}}, *p[2], (*q)[][2];\\ & & q=p[1]=&n[0][1];\\ & & printf("%d, %d\n", p[1][0], (*q)[1][0]);\\ & & return 0;\\ & } </tex> 問2.例2の n, p, q について 1. ***q の値を示せ. 2.「n[1]=p[1];」がエラーとなる理由を述べよ. 以下では簡単のため「A==B && B==C && C==D」を <tex> 『 A == B == C == D 』 </tex> のように略記します.例2のプログラムでは p[1]==&n[0][1] ですから, <tex> 『 p[1][0] == *p[1] == *(\&n[0][1]) == n[0][1] == 5 』 </tex> です.q の方は初期値 &n[0][1] を用いて <tex> \&(*q)[0][0]==*(\&n[0][1]), (*q)[1][0]==*(\&n[0][1]+2*1+0) </tex> と解釈され, <tex> **(*q) == n[0][1], (*q)[1][0] == n[1][1] </tex> となります.『 q == *q == **q == &n[0][1] 』であることは分かり難いの ですが,「(*q)[i][j]==q[0][i][j]」であり「int m[2][3][4];」のときの <tex> 『 m == *m == **m == \&m[0][0][0], ***m == m[0][0][0] 』 </tex> と類似の関係になっています.なお n[1] はアドレスであって(ポインタ とは異なり)メモリ上に領域が与えられていないので,値を代入することは できません. <tex> & n &『n[0][0]=4;』&『n[0][1]=5;』&『n[1][0]=6;』&『n[1][1]=7;』\\ & p &『p[0] 』&『p[1]=(※);』\\ & \&q &『q=p[1]; 』\\ & & (※)==\&n[0][1], (*q)[0][0]==*p[1], (*q)[0][1]==*(p[1]+1)\\ </tex> 文字列とポインタ ============================================================ Cには string 型がないため,文字列は先頭をアドレス,末尾を制御文字'\0' で表わします.例えば,宣言「char s[]="ABC";」は <tex> char s[4]={'A', 'B', 'C', '\0'}; </tex> を略記したもので,先頭のアドレスが s で s[3]=='\0' になっています. 「char *p="ABC";」という宣言は少し分かりにくいのですが,適当な場所に "ABC" を格納する配列をつくり,その先頭アドレスを p に設定します. 文法的には正しくありませんが『p==&"ABC", *p=='A'』という感じです. 例3.次のプログラムの出力は「B, DE」. <tex> & #include <stdio.h>\\ & int main(void)\\ & {\\ & & char s[2][8]={"ABC", "DE"}, (*p)[8]=s;\\ & & printf("%c, %s\n", p[0][1], p[1]);\\ & & return 0;\\ & }\\ </tex> 問3.例3の s, p について 1. (*p)[2] の値を示せ. 2. *(*p+1) の値を示せ. 「char (*p)[8]=s;」で p に設定されるのは s です.p は『char[8]』 へのポインタなので p[i]==s[i], p[i][j]==s[i][j] が成立し, p[0][1]=='B',p[1]==s[1] です.また <tex> (*p)[2]==p[0][2], *p+1==p[0]+1, *(p[0]+1)==p[0][1] </tex> ですから,(*p)[2]=='C', *(*p+1)=='B' です.「char (*q)[4]=s;」と 宣言された q では q[1][0]==s[0][4], q[2][0]==s[1][0] です. 仮引数での宣言 ============================================================ Cでは(値呼びしかできないため)関数の引数にポインタが多用されます. ポインタ p が配列と関係なければ単に *p と表わせばいいので,ここでは 直接あるいは間接に配列を指すポインタについて考えます.実引数としたい 配列と型を合わせたいときは <tex> & int n1[4]; & --> (int p1[]) & または (int *p1)\\ & int n2[4][5]; & --> (int p2[][5]) & & int n3[4][5][6]; & --> (int p3[][5][6]) & & int *m1[4]; & --> (int *q1[]) & または (int **q1) & int *m2[4][5]; & --> (int *q2[][5]) & </tex> のように宣言します.p1, p2, p3, q1, q2 がすべて配列ではなくポインタ であることは,関数内で sizeof(p1) 等を printf() で表示することによって 確認できます. 例4.次のプログラムの出力は「B, D」. <tex> & #include <stdio.h>\\ & void check(char p2[][8], char *q1[])\\ & {\\ & & printf("%c, %c\n", p2[0][1], *q1[1]);\\ & }\\ & \\ & int main(void)\\ & {\\ & & char s[2][8]={"ABC", "DE"}, *r[2];\\ & & r[0]=s[0]; r[1]=s[1]; check(s, r);\\ & & return 0;\\ & }\\ </tex> 問4.例4の p, q について 1. **q1 の値を示せ. 2. q1[1][2] の値を示せ. 仮引数の宣言の [] を [N](N は適当な定数)で置換した大域変数や局所 変数の宣言を考えると,配列要素のアドレスの計算に N は不要であることが 分かります.「char s[2][8];」のとき &s[i][j]-s[0][0]==8*i+j ですが, 仮引数の宣言「int p2[][8]」でも同様に <tex> \&p2[i][j]-\&p2[0][0]==8*i+j </tex> です.したがって p2[0][1]==s[0][1] となります.また「char *r[2];」の r[i] は実在するポインタ,「char *q1[]」の q1[i] は計算したアドレス ですが, <tex> q1[i]==*(q1+i), q[i][j]==*(q1[i]+j) </tex> なので,q1==r であれば q1[i][j]==r[i][j] が成立します.このとき *q1[1]==r[1][0], **q1==r[0][0], q1[1][2]='\0' です.宣言として [#t9cb0413] 「char *q1[]」の代わりに「char **q1」を用いても同じですが,局所変数に 対しては「char **q;」を「char *q[];」のように宣言することはできません. なお,必ずしも実引数と仮引数の型を合わせるのがよいとは限りません. 一例を次に示します. <tex> & #include <stdio.h>\\ & extern int sum, n[10][10][10];\\ & void add(int k, int *p)\\ & {\\ & & while(k>0){sum+=*p; p++; k--;}\\ & }\\ & \\ & int main(void)\\ & {\\ & & sum=0; add(1000, n); printf("sum=%d\n", sum);\\ & & return 0;\\ & }\\ </tex> 構造体メンバの参照 ============================================================ 構造体のメンバ x.m は p==&x であるポインタを用いて p->m で参照でき ます.このことに詳しくない人は,まず構造体の基礎を学んでください. 例5.次のプログラムの出力は「ADC」. <tex> & typedef struct{char s[8], *p;} str2;\\ & int main(void)\\ & {\\ & & str2 x[2]={{"ABC"}, {"DE"}}, *q=x+1;\\ & & x[0].p=x[1].s; x[1].p=x[0].s; x[0].s[1]=q->s[0];\\ & & printf("x[0].s=%s\n", x[0].s);\\ & & return 0;\\ & }\\ </tex> 問5.例5の x, p について 1. q->s[1] の値を示せ. 2. q->p[1] の値を示せ. 「x[1].p=x[0].s;」実行後に x[0].s[i] を書き換えても x[1].p[i] も 変わるので,q->p[1]=='D' です. 補遺 ============================================================ ポインタの性質を理解するときの留意点は,ポインタが参照するデータ の構造はポインタの宣言のみで決まるということです.例えば int n[2][3], *p=&n[0][1]; と宣言されていれば,『(int*) p=&n[0][1];』なので p[-1]==n[0][0], *p==n[0][1], p[1]==n[0][2] となります.つまり p から見れば p[i] は n に関係なく p+i にある データ *(p+i) に過ぎないのです.細かい補足は以下に列挙します. 1. 「int n[][2]={4, 5, 6};」は「int n[2][2]={{4, 5}, {6, 0}};」 として扱われます.「int p[];」や「int p[][2];」をポインタの宣言に 使えないのはのは,このことが関係しているものと思われます.配列要素を 参照するときは「(*p)[i][j]」より「p[0][i][j]」の方が分かりやすい でしょう.参照先によっては「p[k][i][j]」も使えます. 2. 「char *p;」は「char* p;」と同じですが,「char* p, q;」は 「char *p, q;」として処理されます.「char* p;」「char* q;」を まとめるには「typedef char *cptr; cptr p, q;」のようにします. 3. Cのバイブル[1]に複雑な宣言の例として <tex> char (*(*x[3])())[5]; </tex> が挙げられていますが,これを <tex> 「 char & (*(*x[3])())[5]; 」\\ 『 char[5] & *(*x[3])(); 』\\ 『 char[5]* & (*x[3])(); 』\\ 『 ()(char[5]*) & *x[3]; 』\\ 『 (()(char[5]*))* & x[3]; 』\\ 『 (()(char[5]*))*[3] & x; 』\\ </tex> と変形すると,x が「array [3] of pointer to function returning pointer to array [5] of char」であることが少し分かりやすくなるかも 知れません(すぐれた教育用言語である Pascal での表現に近づきます). 4. 自分でプログラムを書くときは,なるべく素直な表現を使いましょう. scanf() で 変数 x[i][j] にデータを入力するとき,&x[i][j] の方が x[i]+j より素直です.ポインタの更新も独立した文で行いましょう. 言語仕様からいえば「printf("%d, %d\n", i++, n[i]);」の結果は処理系に 依存します. あとがき ============================================================ 教科書・参考書を謙虚に学ぶことが基本ですが,疑問点を自力で解決しよう とする姿勢も重要です.このことを奨励するために,比較的取り組みやすい 例を示しました. 参考文献 [1] B. W. カーニハン,D. M. リッチー著,石田晴久訳,プログラミング言語C 第2版,共立出版,1989,ISBN4-320-02483-4. [2] 手抜き用チェックプログラム, [3](ほとんど文献調査を行っていません.引用すべき資料をご教示頂ければ 幸いです.) @@author: pulsar@@ @@accept: 執筆中@@ @@category: プログラミング@@