c言語初学者の例にもれずポインタ迷子になりました。
・ポインタのポインタ
・ポインタの配列
・配列のポインタへのキャスト
あたりを勉強し始めたくらいです。
(既に呼び方が合ってるかも怪しい・・・)
参考にしたページ↓
配列とポインタの完全制覇
動作を確認しながら現状の理解をメモ。
(おそらく間違いだらけです。コメントでご指摘もらえるとうれしいです。)
#「配列の配列」とポインタ
以下の配列の配列(多次元配列)とポインタを使っていろいろ動作確認。
char ss[5][6] = {"abc", "cdef", "fgh", "hijkl", "lm"};
char *p = (char *)ss;
【現状の理解】
ss[5][6] ・・・要素数が5で、「要素数6のchar型配列」型の配列
*p ・・・charへのポインタ型
(char *)ss ・・・ssを(charへのポインタ型)にキャスト。つまり&ss[0][0]
以下のプログラムでポインタが保持するアドレスの値を確認
for(i = 0; i < 10; i++){
printf("*p + %d = %c " , i, *p + i);
printf("*(p + %d) = %c" , i, *(p + i));
}
*p + 0 = a *(p + 0) = a
*p + 1 = b *(p + 1) = b
*p + 2 = c *(p + 2) = c
*p + 3 = d *(p + 3) =
*p + 4 = e *(p + 4) =
*p + 5 = f *(p + 5) =
*p + 6 = g *(p + 6) = c
*p + 7 = h *(p + 7) = d
*p + 8 = i *(p + 8) = e
*p + 9 = j *(p + 9) = f
結果から、
・*p は、s[0][0]の先頭アドレスの値なので'a'
・*p + 1 は、'a' + 1 と同じことを指している。つまり*pはchar型。
・*(p + i)も、もちろんchar型
・*(p + i)は ポインタ*pの先頭アドレスからi個進んだ先頭アドレスを指したうえで、その値を示している(値はchar型)
・ポインタの示す先頭アドレスに「*」をつけると、そのアドレスの値を示す
ついでに、サイズも確認
printf("sizeof(*p) = %u\n", (unsigned)sizeof(*p));
printf("sizeof( p) = %u\n\n", (unsigned)sizeof(p));
sizeof(*p) = 1
sizeof( p) = 8
・*pはchar型なので、1byte
・ pはアドレスを格納しているので、アドレスのサイズが表示される。(アドレスの中身の型は無関係)この処理系ではアドレスは8byte表示
#ポインタの配列
今度はポインタの配列を使って動作確認
char *tt[5] = {"ABC", "CDEF", "FGH", "HIJKL", "LM"};
char **q = tt;
【現状の理解】
*tt[5] ・・・charへのポインタ型の配列。要素数は5
**q ・・・charへのポインタへのポインタ型
以下のプログラムでアドレスやら値やらを確認。
for(i = 0; i < 5; i++){
printf("q + %d = %p " , i, q + i);
printf("*q + %d = %5s " , i, *q + i);
printf("*(q + %d) = %5s " , i, *(q + i));
printf("*(q + %d) + %d = %5s " , i, i, *(q + i) + i);
printf("**q + %d = %c " , i, **q + i);
printf("*(*(q + %d) + %d) = %c" , i, i, *(*(q + i) + i));
putchar('\n');
}
q + 0 = 000000000061FDE0 *q + 0 = ABC *(q + 0) = ABC *(q + 0) + 0 = ABC **q + 0 = A *(*(q + 0) + 0) = A
q + 1 = 000000000061FDE8 *q + 1 = BC *(q + 1) = CDEF *(q + 1) + 1 = DEF **q + 1 = B *(*(q + 1) + 1) = D
q + 2 = 000000000061FDF0 *q + 2 = C *(q + 2) = FGH *(q + 2) + 2 = H **q + 2 = C *(*(q + 2) + 2) = H
q + 3 = 000000000061FDF8 *q + 3 = *(q + 3) = HIJKL *(q + 3) + 3 = KL **q + 3 = D *(*(q + 3) + 3) = K
q + 4 = 000000000061FE00 *q + 4 = CDEF *(q + 4) = LM *(q + 4) + 4 = s[5][6]: **q + 4 = E *(*(q + 4) + 4) = s
実行結果の考察
・q + i は、ポインタのポインタが指す先頭アドレスからi個すすんだところのアドレス(格納されている値がアドレスなので、8byteずつすすむ)
・*q + i は、ポインタのポインタが指すアドレスからi個すすんだところのアドレスの中身(qはcharへのポインタ型なので、+1すると1byteすすむ。)
・%s は、指定されたアドレスの文字から、終端文字までの文字を出力する
・*q + 3 は終端文字、*q + 4 は次の文字列になっているということは、文字列のサイズはバラバラになっている。もし各文字列のサイズが同じなら、*q + 4 にも終端文字が格納されているはず。(配列では空いたところにはNULL文字が入る)
・ポインタには「要素数」という概念がない。
・(q + i)は、ポインタのポインタが指す先頭アドレスからi個すすんだところのアドレス中身(初期化の時点でq + 4 まではアドレスを格納してある)
・**q + i は、ポインタのポインタが指すアドレスの中身の中身ニ +1した値。(**qは純粋なchar型の値なので、文字コードに従った出力になる)
・文字列中の'a'と文字コードの'a'の指すアドレスは違う。(これ重要!)
サイズは以下のような感じ
printf("sizeof(q) = %u\n", (unsigned)sizeof(q));
printf("sizeof(*q) = %u\n", (unsigned)sizeof(*q));
printf("sizeof(**q) = %u\n\n", (unsigned)sizeof(**q));
sizeof(q) = 8
sizeof(*q) = 8
sizeof(**q) = 1
q・・・charへのポインタへのポインタ型なので先頭アドレスのサイズを指す。
*q・・・charへのポインタのポインタ型の中身(*)なので、中身のcharへのポインタ型の先頭アドレスのサイズを指す。
**q・・・charへのポインタのポインタ型の中身(*)の中身(*)なので、中身のcharへのポインタ型の先頭アドレスの中身の値(char型)のサイズを指す。
ああ、日本語が崩壊していく・・・
#配列とポインタについてのまとめ(現状の理解)
配列
・値を格納する(アドレスの書き換えはダメ!)
・要素数がある(その分、要素数の乗算でサイズが大きくなる)
⇒ 自由度が低く、管理コストがかかるが、扱いは簡単なイメージ
(20人を5チームに分けるので4人ずつ整列しなさい!的な)
ポインタ
・アドレスを格納する(つまり占有領域が8バイトしかなくてうれしい!)
・要素数がない(配列のように使う時も、あくまで「アドレスの指定」という概念)
⇒ 自由度が高く、管理コストが少なくてすむが、扱いは難しいイメージ
(とりあえず少人数のチームを何個か作りたいんだよねえ。リーダーはお前!あとは適当にやってちょ~。的な。)
##思った事、メモ
命令単体での動作確認だけじゃ、深い理解にはならないと思った。(もちろんそれもめちゃくちゃ大切だが。)
基礎知識がついたら簡単なアプリケーションを組まないとなあ。
ポインタを勉強して分からなくなるまでの道のりをメモ
・は~ん、ポインタってアドレスを指すのね。以外に簡単じゃん。
・実は配列もアドレスを指してたのね。なるほど。
・あれ、ポインタと配列って同じじゃんか!
・ポインタと配列同じじゃないな・・・違いはアドレスを格納する領域があるか、ないかか。簡単じゃん!
・ポインタのポインタ・・・ポインタの配列・・・なんだか気分が悪くなってきた。オェッ。
・ポインタのサイズっていつも8byte。配列と違って「要素数」とかいう概念もないのね。
・は~ん、ポインタってアドレスを指すのね。なんとなく分かった・・・気がする・・・【←イマココ】