Download presentation
Presentation is loading. Please wait.
Published byNathalie Macedo de Andrade Modified 約 6 年前
1
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ, 情報処理言語Ⅰ(実習を含む。)
C言語入門 第13週 プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ, 情報処理言語Ⅰ(実習を含む。)
2
備考 マルチバイトデータの各バイトはどう並ぶか? メモリ上におけるデータの並び
3
変数の割り当てとバイトオーダー 32bit int型の場合 int a; // 変数の宣言 備考 : 0x?? 0x~00 0x??
4
変数の割り当てとバイトオーダー 32bit int型の場合(Little Endian) int a = 0x12345678;
備考 変数の割り当てとバイトオーダー Intel の x86 系 CPU 等 32bit int型の場合(Little Endian) : int a = 0x ; // 変数の宣言と初期化 0x78 0x~00 0x56 0x~01 32bit 0x34 0x~02 0x12 0x~03 0x?? 0x~04 0x?? 0x~05 0x?? 0x~06 a 0x 0x?? 0x~07 0x?? 0x~08 :
5
変数の割り当てとバイトオーダー 32bit int型の場合(Big Endian) int a = 0x12345678;
備考 変数の割り当てとバイトオーダー ネットワーク上を流れるデータ等 32bit int型の場合(Big Endian) : int a = 0x ; // 変数の宣言と初期化 0x12 0x~00 0x34 0x~01 32bit 0x56 0x~02 0x78 0x~03 0x?? 0x~04 0x?? 0x~05 0x?? 0x~06 a 0x 0x?? 0x~07 0x?? 0x~08 :
6
変数の割り当てとバイトオーダー 16bit short型の場合(Little Endian) short a = 0x1234;
備考 変数の割り当てとバイトオーダー Intel の x86 系 CPU 等 16bit short型の場合(Little Endian) : short a = 0x1234; // 変数の宣言と初期化 0x34 0x~00 16bit 0x12 0x~01 0x?? 0x~02 a 0x1234 0x?? 0x~03 0x?? 0x~04 0x?? 0x~05 同じ場所から32bitで取ると 0x?? 0x~06 a 0x????1234 0x?? 0x~07 0x?? 0x~08 :
7
変数の割り当てとバイトオーダー 16bit short型の場合(Big Endian) short a = 0x1234;
備考 変数の割り当てとバイトオーダー ネットワーク上を流れるデータ等 16bit short型の場合(Big Endian) : short a = 0x1234; // 変数の宣言と初期化 0x12 0x~00 16bit 0x34 0x~01 0x?? 0x~02 a 0x1234 0x?? 0x~03 0x?? 0x~04 0x?? 0x~05 同じ場所から32bitで取ると 0x?? 0x~06 a 0x1234???? 0x?? 0x~07 0x?? 0x~08 :
8
メモリのアドレスを用いて格納値を操作する
ポインタ
9
ポインタ変数 メモリ上のアドレスを指し示すための変数 ポインタ型のサイズはアドレス空間のビット数
教科書pp ポインタ変数 pointer: 指し示す者 メモリ上のアドレスを指し示すための変数 char * 型: char 型の変数へのポインタ int * 型: int 型の変数へのポインタ 要はアドレスを格納するための変数 ポインタ型のサイズはアドレス空間のビット数 sizeof(char *)もsizeof(int *)も同じ 32ビットアドレス空間なら4バイト 64ビットアドレス空間なら8バイト
10
メモリの構成 1byte単位でアドレスが振られている つまり各アドレスには1byteの値を格納出来る 教科書 pp.52-56.
32bitのOSは32bitのアドレス空間 最大 2 32 Bytes=4GiB 64bitのOSは64bitのアドレス空間 最大 2 64 Bytes=16EiB 0x00 0x 0x 0x 0x 0xffffffff : 0x00 0x 0x 0x 0x 0xffffffffffffffff : ポインタ変数には これらの値が入る アドレス 格納値 アドレス 格納値
11
ポインタ変数 例: 16bit short型little endianの場合 short a = 0x1234;
教科書pp ポインタ変数 宣言時に*を付けると ポインタ変数になる 要はリンク みたいなもの 例: 16bit short型little endianの場合 : short a = 0x1234; short *p = &a; &a 0x34 0x~00 a 0x12 0x~01 p に &a を入れておくと *p は a への 参照として機能する C言語ではこれを ポインタと言う 0x?? 0x~02 &aは 変数aの メモリ上の 先頭アドレス a 0x1234 0x?? 0x~03 0x~00 0x?? 0x~04 0x?? 0x~05 *p 0x1234 0x?? 0x~06 *pはアドレスpに 置かれた変数(ここではa) への参照 0x~00 0x?? 0x~07 0x?? 0x~08 p 0x~00 実際に存在している変数はpで中にはアドレスが格納されている : pは変数*pの アドレスを指し示す
12
ポインタ変数 変数が配置されたアドレスを扱うための変数 教科書pp.207-272. 変数の宣言で
pointer_ex1_1.c 変数の宣言で 変数名の前にポインタ宣言子「*」を付けると ポインタ変数になる。 4 5 6 7 8 9 10 11 12 13 14 15 16 int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d\n", *p); printf(" a=%d\n", a); return EXIT_SUCCESS; } ポインタ変数 p に 変数 a のアドレス &a を代入すると 変数 a の代わりに *p が使える。 mintty + bash + GNU C $ gcc pointer_ex1_1.c && ./a *p=2 a=2 重要
13
ポインタ変数 変数が配置されたアドレスを扱うための変数 教科書pp.207-272. 変数の宣言で
pointer_ex1_1.c 変数の宣言で 変数名の前にポインタ宣言子「*」を付けると ポインタ変数になる。 この場合、 int 型の変数 *p を宣言した結果、 実際には int 型変数へのポインタである int * 型変数 p が定義された と理解しておくとスッキリする。 4 5 6 7 8 9 10 11 12 13 14 15 16 int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d\n", *p); printf(" a=%d\n", a); return EXIT_SUCCESS; } mintty + bash + GNU C $ gcc pointer_ex1_1.c && ./a *p=2 a=2 重要
14
ポインタ変数 変数が配置されたアドレスを扱うための変数 教科書pp.207-272. アドレス演算子「&」を用いて
pointer_ex1_1.c 4 5 6 7 8 9 10 11 12 13 14 15 16 int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d\n", *p); printf(" a=%d\n", a); return EXIT_SUCCESS; } アドレス演算子「&」を用いて int 型変数 a のアドレス &a を int 型の変数へのポインタ p に代入する a 1 0x~ mintty + bash + GNU C &a p 0x~ $ gcc pointer_ex1_1.c && ./a *p=2 a=2
15
ポインタ変数 変数が配置されたアドレスを扱うための変数 教科書pp.207-272. int 型の変数へのポインタ p の前に
pointer_ex1_1.c 4 5 6 7 8 9 10 11 12 13 14 15 16 int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d\n", *p); printf(" a=%d\n", a); return EXIT_SUCCESS; } int 型の変数へのポインタ p の前に 間接演算子「*」を付けて *p とすると ポインタ変数 p が指し示すアドレスを int 型の変数としてアクセス出来る。 今 p には変数 a のアドレス &a が 入っているので、 *p は変数 a を意味するようになる 2 *p⇒a 1 0x~ mintty + bash + GNU C &a p 0x~ $ gcc pointer_ex1_1.c && ./a *p=2 a=2 実際 *p に代入した値 2 が a に反映されていることが確認出来る。
16
ポインタ変数 変数が配置されたアドレスを扱うための変数 教科書pp.207-272. ポインタ変数pには 変数aのアドレス&aが入っている
pointer_ex1_1.c 4 5 6 7 8 9 10 11 12 13 14 15 16 int main() { int a = 1; int *p; p = &a; *p = 2; printf("*p=%d\n", *p); printf(" a=%d\n", a); return EXIT_SUCCESS; } ポインタ変数pには 変数aのアドレス&aが入っている この状態で*pは アドレス&aにある変数 つまり変数aを意味する。 2 *p⇒a 1 &a⇒ p 0x~ 0x~ mintty + bash + GNU C $ gcc pointer_ex1_1.c && ./a *p=2 a=2
17
ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる
教科書pp , [1] pp.250, ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる 宣言時 それ以外 *p pをポインタとして宣言(※1) *pの格納値(※2) *p=x pにxを代入(※3) *pにxを代入(※4) pointer_ex1_1.c pointer_ex1_2.c int a = 1; int *p; // ※1 p = &a; *p = 2; // ※4 printf("*p=%d\n", *p);// ※2 int a = 1; int *p = &a; // ※3 *p = 2; // ※4 printf("*p=%d\n", *p);// ※2 要注意
18
ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる
教科書pp , [1] pp.250, ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる 宣言時 それ以外 *p pをポインタとして宣言(※1) *pの格納値(※2) *p=x pにxを代入(※3) *pにxを代入(※4) pointer_ex1_1.c pointer_ex1_2.c int型の変数 *p を宣言した 実際に作成されたのは int型のポインタ変数 p int a = 1; int *p; // ※1 p = &a; *p = 2; // ※4 printf("*p=%d\n", *p);// ※2 int a = 1; int *p = &a; // ※3 *p = 2; // ※4 printf("*p=%d\n", *p);// ※2 pが指すアドレスの格納値 要注意
19
ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる
教科書pp , [1] pp.250, ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる 宣言時 それ以外 *p pをポインタとして宣言(※1) *pの格納値(※2) *p=x pにxを代入(※3) *pにxを代入(※4) pointer_ex1_1.c pointer_ex1_2.c int a = 1; int *p; // ※1 p = &a; *p = 2; // ※4 printf("*p=%d\n", *p);// ※2 int a = 1; int *p = &a; // ※3 *p = 2; // ※4 printf("*p=%d\n", *p);// ※2 特に※3に注意 *p に &a を代入している のではなく p に &a を代入している この2つの代入は 同じ処理を意味する 要注意
20
ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる
教科書pp , [1] pp.250, ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる 宣言時 それ以外 *p pをポインタとして宣言(※1) *pの格納値(※2) *p=x pにxを代入(※3) *pにxを代入(※4) int型の変数 *p を宣言して int型のポインタ変数 p に p = &a と代入 つまりpが指すアドレスを&aに pointer_ex1_1.c pointer_ex1_2.c int a = 1; int *p; // ※1 p = &a; *p = 2; // ※4 printf("*p=%d\n", *p);// ※2 int a = 1; int *p = &a; // ※3 *p = 2; // ※4 printf("*p=%d\n", *p);// ※2 pが指すアドレスの格納値 つまり変数aの格納値を2に 要注意
21
ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる
教科書pp , [1] pp.250, ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 同じ「*」だが宣言時とそれ以外で意味が異なる 宣言時 それ以外 *p pをポインタとして宣言(※1) *pの格納値(※2) *p=x pにxを代入(※3) *pにxを代入(※4) *は間接演算子 indirection operator または間接参照演算子 dereference operator ポインタ変数pが指すアドレスの中身 (言い換えると、ある型の変数*p)を 操作(参照or代入)する ポインタ変数pの宣言 (言い換えると、ある型の変数*pを宣言)し 宣言した変数pに対して アドレスxを代入している *はポインタ宣言子 pointer declarator 要注意
22
ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 教科書pp.207-272. 要注意
pointer_ex2.cpp 実際に *p1 と p1 のどちらが &a になっているか 確認してみましょう。 6 7 8 9 10 11 12 13 14 15 16 17 int a = 1; int *p1 = &a; int *p2; p2 = &a; printf("&a =%#0*p\n", sizeof(&a )*2+2, &a ); printf(" p1=%#0*p\n", sizeof( p1)*2+2, p1); printf(" p2=%#0*p\n", sizeof( p2)*2+2, p2); printf(" a =%d\n", a ); printf("*p1=%d\n", *p1); printf("*p2=%d\n", *p2); mintty + bash + GNU C $ gcc pointer_ex2.c && ./a &a =0x cb0c p1=0x cb0c p2=0x cb0c a =1 *p1=1 *p2=1 訂正 誤:q2=0x cb0c 正:p2=0x cb0c 要注意
23
ポインタ変数の宣言と代入 「*」と「=」の使い方は要注意 教科書pp.207-272. 要注意 宣言時は「*」が間接演算子ではなく
ポインタ宣言子として働いている 宣言時に初期化すると *p1 ではなく p1 に &a を代入している pointer_ex2.cpp 6 7 8 9 10 11 12 13 14 15 16 17 int a = 1; int *p1 = &a; int *p2; p2 = &a; printf("&a =%#0*p\n", sizeof(&a )*2+2, &a ); printf(" p1=%#0*p\n", sizeof( p1)*2+2, p1); printf(" p2=%#0*p\n", sizeof( p2)*2+2, p2); printf(" a =%d\n", a ); printf("*p1=%d\n", *p1); printf("*p2=%d\n", *p2); *p1 ではなく p1 に &a が 代入される事が確認出来る p2 に &a を代入している mintty + bash + GNU C $ gcc pointer_ex2.c && ./a &a =0x cb0c p1=0x cb0c p2=0x cb0c a =1 *p1=1 *p2=1 宣言時以外は「*」が関節演算子として働く ポインタの前に*が付くと、 指し示すアドレスに格納されている値が 操作の対象となる 要注意
24
備考: ポインタの表示 %p void *;ポインタとして印字(処理系依存) 第3週資料pp.24-33. %*pによる最小フィールド幅指定
pointer_ex2.cpp mintty + bash + GNU C 11 printf("&a =%#0*p\n", sizeof(&a )*2+2, &a ); $ gcc pointer_ex2.c && ./a &a =0x cb0c p1=0x cb0c p2=0x cb0c a =1 *p1=1 *p2=1 %*pによる最小フィールド幅指定 1バイト=16進数2桁 先頭の0xで更に2桁 #: 16進表示が0でない場合、先頭を0xにする 0: フィールド幅いっぱいに左側から0を詰める *: 最小フィールド幅を引数で指定 p: void *;ポインタとして印字
25
ポインタ変数の宣言と代入 宣言時の「*」が係る場所にも注意 教科書pp.207-272.
サイズが違うので p,q が別の型だと分かる 宣言時の「*」が係る場所にも注意 pointer_ex3_1.cpp Cygwin64 + bash + GNU C 6 7 8 9 int *p, q; printf("sizeof(p)=%d\n", sizeof(p)); printf("sizeof(q)=%d\n", sizeof(q)); $ gcc pointer_ex3_1.c && ./a sizeof(p)=8 sizeof(q)=4 p は「int *」型の宣言(int型へのポインタ変数) q は「int 」型の宣言(int型の変数) 複数の変数を同時に宣言する場合 変数名の直前に ポインタ宣言子「*」を付けた変数だけが ポインタ変数になる 要注意 p,q 共にポインタとして 宣言するには int *p, *q; または int *p; int *q; とする必要がある。 要注意
26
ポインタ変数の宣言と代入 宣言時の「*」が係る場所にも注意 教科書pp.207-272.
pointer_ex3_1.cpp Cygwin64 + bash + GNU C 6 7 8 9 int *p, q; printf("sizeof(p)=%d\n", sizeof(p)); printf("sizeof(q)=%d\n", sizeof(q)); int 型の変数 *p と q を宣言している $ gcc pointer_ex3_1.c && ./a sizeof(p)=8 sizeof(q)=4 p は「int *」型の宣言(int型へのポインタ変数) q は「int 」型の宣言(int型の変数) 複数の変数を同時に宣言する場合 変数名の直前に ポインタ宣言子「*」を付けた変数だけが ポインタ変数になる 要注意 p,q 共にポインタとして 宣言するには int *p, *q; または int *p; int *q; とする必要がある。 int 型の変数 *p と *q を宣言している 要注意
27
ポインタ変数 教科書pp.207-272. pointer_ex4.c Cygwin64+mintty+bash+GNU C
8 9 10 11 12 13 14 15 16 17 18 int i, *p, a[] = {0,1,2,3,4,5,6,7,8,9}; printf("&a[0]=%#0*p\n", sizeof(&a[0])*2+2, &a[0]); printf("p = ? "); scanf("%p", &p); printf("*p = ? "); scanf("%i", p); for (i = 0; i < sizeof(a) / sizeof(*a); i++) { printf("a[%d]=%#0*x\n", i, sizeof(*a)*2+2, a[i]); } $ gcc pointer_ex4.c && ./a &a[0]=0x aa90 p = ? 0x aa98 *p = ? 0x a[0]= a[1]=0x a[2]=0x a[3]=0x a[4]=0x a[5]=0x a[6]=0x a[7]=0x a[8]=0x a[9]=0x 結果がどうなるかは別として アドレス演算子を使って 変数のアドレスを入れなくても 適当な値を入れても動く。
28
ポインタ変数 教科書pp.207-272. pointer_ex4.c Cygwin64+mintty+bash+GNU C
8 9 10 11 12 13 14 15 16 17 18 int i, *p, a[] = {0,1,2,3,4,5,6,7,8,9}; printf("&a[0]=%#0*p\n", sizeof(&a[0])*2+2, &a[0]); printf("p = ? "); scanf("%p", &p); printf("*p = ? "); scanf("%i", p); for (i = 0; i < sizeof(a) / sizeof(*a); i++) { printf("a[%d]=%#0*x\n", i, sizeof(*a)*2+2, a[i]); } $ gcc pointer_ex4.c && ./a &a[0]=0x aa90 p = ? 0x aa92 *p = ? 0x a[0]=0x a[1]=0x a[2]=0x a[3]=0x a[4]=0x a[5]=0x a[6]=0x a[7]=0x a[8]=0x a[9]=0x 結果が正しいかどうかは別として 配列の途中のアドレスを ポインタに入れることも出来る
29
値渡しと参照渡し(ポインタ渡し)の用途等
関数の引数とポインタ
30
関数の引数(値渡し、参照渡し) 変数のスコープ(有効範囲) $ gcc scopetest.c && ./a
第8週資料pp 関数の引数(値渡し、参照渡し) 変数のスコープ(有効範囲) scopetest.c 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int gl = 100; void sub(int lo) { int lo = 400; printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo); } int main() int lo = 200; sub(300); return EXIT_SUCCESS; 関数の引数は呼び出し元とは別の変数になっていた mintty + bash + GNU C $ gcc scopetest.c && ./a sub : 14: gl=101, lo=401 sub : 16: gl=102, lo=301 main : 23: gl=103, lo=201
31
関数の引数(値渡し、参照渡し) 値渡し: 呼出し元の値のコピーを渡す $ gcc call_by_value.c && ./a lo=100
第8週資料pp 教科書p.171. 関数の引数(値渡し、参照渡し) 値渡し: 呼出し元の値のコピーを渡す call_by_value.c 4 5 6 7 8 9 10 11 12 13 14 15 16 void sub(int lo) { lo = 200; } int main() int lo = 100; sub(lo); printf("lo=%d\n", lo); return EXIT_SUCCESS; 引数で受け取った変数を変更しても 呼び出し元には反映されない mintty + bash + GNU C $ gcc call_by_value.c && ./a lo=100
32
関数の引数(値渡し、参照渡し) 参照渡し: 呼出し元の値の格納場所を渡す $ gcc call_by_pointer.c && ./a
これは正確には ポインタ渡しと言う 4 5 6 7 8 9 10 11 12 13 14 15 16 void sub(int *lo) { *lo = 200; } int main() int lo = 100; sub(&lo); printf("lo=%d\n", lo); return EXIT_SUCCESS; 引数で受け取った変数を変更すると 呼び出し元にも反映される scanf で見たことがある書き方! &: アドレス演算子 変数loのアドレスを 渡している mintty + bash + GNU C $ gcc call_by_pointer.c && ./a lo=200
33
関数の引数(値渡し、参照渡し) C++における参照渡し $ g++ call_by_reference.cpp && ./a lo=200
参考 関数の引数(値渡し、参照渡し) C++における参照渡し call_by_reference.cpp C++では 本物の参照渡しが 可能になった 4 5 6 7 8 9 10 11 12 13 14 15 16 void sub(int &lo) { lo = 200; } int main() int lo = 100; sub(lo); printf("lo=%d\n", lo); return EXIT_SUCCESS; 引数で受け取った変数を変更すると 呼び出し元にも反映される 値が変化することが 分かり難いという デメリットもある C++の参照渡しでは アドレス演算子「&」が不要 mintty + bash + GNU C++ $ g++ call_by_reference.cpp && ./a lo=200
34
call by pointer (ポインタ渡し)
呼び出し元の変数の内容を変更したい場合 swapi_ex1.c swapi_test.c void swapi(int *a, int *b) { int c; c = *a; *a = *b; *b = c; } int a, b; fprintf(stderr, "a = ? "); scanf("%d", &a); fprintf(stderr, "b = ? "); scanf("%d", &b); swapi(&a, &b); printf("a = %d\n", a); printf("b = %d\n", b); void swapi(int *a, int *b); 関数 *a と *b の値を入れ替える
35
call by pointer (ポインタ渡し)
2つ以上の値を返したい場合 戻り値は1つしかないので、関数の引数にポインタを渡して、値を返す modf_ex1.c modf_test.c double modf(double x, double *iptr) { *iptr = x < 0 ? ceil(x): floor(x); return x - *iptr; } double x; double i; double f; fprintf(stderr, "x = ?\b"); scanf("%lf", &x); f = modf(x, &i); printf("i : %f\n", i); printf("f : %f\n", f); double modf(double x, double *iptr); 関数 浮動小数点数を整数部と小数部に分ける 戻り値: x の小数部を戻り値に x の整数部を *iptr に返す 戻り値は共に x と同じ符号を持つ JM: modf (3)
36
call by pointer (ポインタ渡し)
任意の長さの配列を渡したい場合 例えば文字列等 strlen_ex1.c strlen_test.c size_t strlen(const char *s) { size_t len = 0; while(s[len] != '\0') { len++; } return len; char s[1024] = ""; fprintf(stderr, "s = ? "); scanf("%1023[^\n]", &s); printf("strlen(s) = %d\n", strlen(s)); 値が書き変えられては困る場合 const char への * (ポインタ) にしておくと、この関数を使っても 与えた内容が変更されないことを ある程度保証することが出来る size_t strlen(const char *s); 関数 '\0'で終端された文字列の長さを返す
37
const 修飾子 const 型: 変数の値を変更出来なくなる
教科書p.308,310,314., [1] pp.240,257, const 修飾子 const 型: 変数の値を変更出来なくなる const_ex1.c 6 7 8 9 const int i = 0; // 初期化は出来る int const j = 0; // const int も int const も同じ意味 i = 1; // const を付けた変数には代入出来ない j = 1; // const を付けた変数には代入出来ない const 修飾子は型修飾子(type-qualifier)の一種 型修飾子は型名の前後どちらにつけても良い mintty + bash + GNU C $ gcc const_ex1.c const_ex1.c: 関数 ‘main’ 内: const_ex1.c:8:5: エラー: 読み取り専用変数 ‘i’ への代入です i = 1; // Error: i is const ^ const_ex1.c:9:5: エラー: 読み取り専用変数 ‘j’ への代入です j = 1; // Error: j is const 変更しようとするとコンパイル時に エラーになるので 本来書き変えてはいけない値を 書き変えてしまうことで生じるバグを 未然に防げる
38
const 修飾子 const char 型へのポインタ
教科書p.308,310,314., [1] pp.240,257, const 修飾子 const char 型へのポインタ char const * 型 const char * 型 const_ex2.c 6 7 8 9 10 11 char s[] = "hello, world"; const char *p; // ポインタの場合 *p が const になる p = s; // ポインタへは代入出来る *p = 'H'; // ポインタ指し示す先の変数には代入出来ない p[7] = 'W'; // ポインタ指し示す先の変数には代入出来ない *(char *) p = 'H'; // const のない型へ cast してやると代入出来る ただし、わざわざ const を付けているということは 変更してはいけない、 または変更しないことを前提としているのであるから 普通は、特別に理由がない限り無理矢理書き変えてはいけない mintty + bash + GNU C $ gcc const_ex2.c const_ex2.c: 関数 ‘main’ 内: const_ex2.c:9:6: エラー: 読み取り専用位置 ‘*p’ への代入です *p = 'H'; // Error: *p is const ^ const_ex2.c:10:8: エラー: 読み取り専用位置 ‘*(p + 7u)’ への代入です p[7] = 'W'; // Error: p[x] is const
39
const 修飾子 char 型への const ポインタ
教科書p.308,310,314., [1] pp.240,257, const 修飾子 char 型への const ポインタ char * const 型 const_ex3.c 6 7 8 9 10 char s[] = "hello, world"; char *const p = s; // p が const で *p が char になる p = s; // p が const なのでポインタへは代入出来ない *p = 'H'; // ポインタ指し示す先の変数には代入出来る p[7] = 'W'; // ポインタ指し示す先の変数には代入出来る const char *p と char const *p は同じだが char * const p は意味が異なる 前者は *p が const char つまり p は変数で *p が定数 後者は *const p が char つまり p は定数で *p は変数 である mintty + bash + GNU C $ gcc const_ex3.c const_ex3.c: 関数 ‘main’ 内: const_ex3.c:8:5: エラー: 読み取り専用変数 ‘p’ への代入です p = s; // Error: p is const ^ * がどこに係っているか よく考えるましょう
40
1次元配列とポインタの相違点 配列とポインタ
41
ポインタ変数とアドレス演算 例: 16bit short型little endianの場合 short a = 0x1234;
教科書pp ポインタ変数とアドレス演算 例: 16bit short型little endianの場合 : short a = 0x1234; short *pa = &a; pa 0x34 0x~00 *pa 0x12 0x~01 pa+1 0x?? 0x~02 *(pa+1) ±1するとsizeof(*pa)単位で アドレスが増減する つまり short 型配列の 0要素目、1要素目、... という意味になる 0x?? 0x~03 pa+2 0x?? 0x~04 *(pa+2) 0x?? 0x~05 pa+3 0x?? 0x~06 *(pa+3) 0x?? 0x~07 0x?? 0x~08 : 要注意
42
ポインタ変数とアドレス演算 例: 32bit int型little endianの場合 int a = 0x12345678;
教科書pp ポインタ変数とアドレス演算 例: 32bit int型little endianの場合 : int a = 0x ; int *pa = &a; pa 0x78 0x~00 0x56 0x~01 *pa 0x34 0x~02 ±1するとsizeof(*pa)単位で アドレスが増減する つまり int 型配列の 0要素目、1要素目、... という意味になる 0x12 0x~03 pa+1 0x?? 0x~04 0x?? 0x~05 *(pa+1) 0x?? 0x~06 0x?? 0x~07 0x?? 0x~08 : 要注意
43
ポインタ変数の配列的利用法 例: 16bit short型little endianの場合 short a = 0x1234;
教科書pp ポインタ変数の配列的利用法 例: 16bit short型little endianの場合 : short a = 0x1234; short *pa = &a; &pa[0] 0x34 0x~00 pa[0] 0x12 0x~01 &pa[1] 0x?? 0x~02 pa[1] 配列同様[x]で先頭x個目の 要素にアクセス出来る 要素の頭に&を付けると アドレスが得られる 0x?? 0x~03 &pa[2] 0x?? 0x~04 pa[2] 0x?? 0x~05 &pa[3] 0x?? 0x~06 pa[3] 0x?? 0x~07 0x?? 0x~08 :
44
ポインタ変数の配列的利用法 例: 32bit int型little endianの場合 int a = 0x12345678;
教科書pp ポインタ変数の配列的利用法 例: 32bit int型little endianの場合 : int a = 0x ; int *pa = &a; &pa[0] 0x78 0x~00 0x56 0x~01 pa[0] 0x34 0x~02 配列同様[x]で先頭x個目の 要素にアクセス出来る 要素の頭に&を付けると アドレスが得られる 0x12 0x~03 &pa[1] 0x?? 0x~04 0x?? 0x~05 pa[1] 0x?? 0x~06 0x?? 0x~07 0x?? 0x~08 :
45
1次元配列とポインタ 64bit OSで 8bit char 型の例 (little endian) char a[] = {
教科書pp 1次元配列とポインタ 64bit OSで 8bit char 型の例 (little endian) ⋮ char a[] = { 0,1,2,3, 4,5,6,7, }; char*p = a; &p 0x10 0x23cb08 0xcb 0x23cb09 0x23 0x23cb0a 0x00 0x23cb0b p 0x00 0x23cb0c 0x00 0x23cb0d 0x00 0x23cb0e sizeof(p) は OSのアドレス空間のビット数/8 つまりsizeof(char *) &a =a =p 0x00 0x23cb0f 0x00 0x23cb10 a[0] 0x01 0x23cb11 a[1] 0x02 0x23cb12 a[2] &a[4] =&p[4] =p+4 0x03 0x23cb13 a[3] a sizeof(a) は aの1要素当たりのバイト数×要素数 つまりsizeof(char)*N 0x04 0x23cb14 a[4] 0x05 0x23cb15 a[5] 0x06 0x23cb16 a[6] 0x07 0x23cb17 a[7] ⋮
46
1次元配列とポインタ 64bit OSで 16bit short 型の例 (little endian) short a[] = {
教科書pp 1次元配列とポインタ 64bit OSで 16bit short 型の例 (little endian) ⋮ short a[] = { 0x0001,0x0203, 0x0405,0x0607, }; short *p = a; &p 0x10 0x23cb08 0xcb 0x23cb09 0x23 0x23cb0a 0x00 0x23cb0b p 0x00 0x23cb0c 0x00 0x23cb0d 0x00 0x23cb0e &a =a =p 0x00 0x23cb0f 0x01 0x23cb10 a[0] 0x00 0x23cb11 0x03 0x23cb12 a[1] &a[2] =&p[2] =p+2 0x02 0x23cb13 a 0x05 0x23cb14 a[2] 0x04 0x23cb15 0x07 0x23cb16 a[3] 0x06 0x23cb17 ⋮
47
1次元配列とポインタ 64bit OSで 32bit int 型の例 (little endian) int a[] = {
教科書pp 1次元配列とポインタ 64bit OSで 32bit int 型の例 (little endian) ⋮ int a[] = { 0x , 0x , }; int *p = a; &p 0x10 0x23cb08 0xcb 0x23cb09 0x23 0x23cb0a 0x00 0x23cb0b p 0x00 0x23cb0c 0x00 0x23cb0d 0x00 0x23cb0e &a =a =p 0x00 0x23cb0f 0x03 0x23cb10 0x02 0x23cb11 a[0] 0x01 0x23cb12 &a[1] =&p[1] =p+1 0x00 0x23cb13 a 0x07 0x23cb14 0x06 0x23cb15 a[1] 0x05 0x23cb16 0x04 0x23cb17 ⋮
48
1次元配列とポインタ 機能としてはほぼ同じ ポインタはアドレスを変更可能(少し柔軟) 教科書pp.207-272.
配列は1要素のバイト数×要素数 ポインタはアドレス空間のビット数/8 機能としてはほぼ同じ ポインタはアドレスを変更可能(少し柔軟) 配列 int p[N]; ポインタ int *p; sizeof(p) ○ 変数pの割り当てバイト数 &p △ 変数pのアドレス(配列の場合&pとpは同じ) p アドレスp(p[0]のアドレス) *p アドレスpの格納値(p[0]の格納値) p[x] アドレスpを先頭にしてx個目の要素(p[x])の格納値 *(p+x) &p[x] アドレスpを先頭にしてx個目の要素(p[x])のアドレス p+x p+=x ×
49
1次元配列とポインタ 教科書pp.207-272. pointer_ex5.c
cygwin64 + mintty + bash + GNU C 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p\n", sizeof(a)); printf("sizeof(p) = %p\n", sizeof(p)); printf("&a = %p\n", &a); printf("&p = %p\n", &p); printf("a = %p\n", a); printf("p = %p\n", p); printf("*a = %d\n", *a); printf("*p = %d\n", *p); printf("a[x] = %d\n", a[x]); printf("p[x] = %d\n", p[x]); printf("*(a+x) = %d\n", *(a+x)); printf("*(p+x) = %d\n", *(p+x)); printf("&a[x] = %p\n", &a[x]); printf("&p[x] = %p\n", &p[x]); printf("a+x = %p\n", a+x); printf("p+x = %p\n", p+x); //printf("a+=x = %p\n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p\n", p+=x); $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4
50
1次元配列とポインタ 教科書pp.207-272. pointer_ex5.c
cygwin64 + mintty + bash + GNU C 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p\n", sizeof(a)); printf("sizeof(p) = %p\n", sizeof(p)); printf("&a = %p\n", &a); printf("&p = %p\n", &p); printf("a = %p\n", a); printf("p = %p\n", p); printf("*a = %d\n", *a); printf("*p = %d\n", *p); printf("a[x] = %d\n", a[x]); printf("p[x] = %d\n", p[x]); printf("*(a+x) = %d\n", *(a+x)); printf("*(p+x) = %d\n", *(p+x)); printf("&a[x] = %p\n", &a[x]); printf("&p[x] = %p\n", &p[x]); printf("a+x = %p\n", a+x); printf("p+x = %p\n", p+x); //printf("a+=x = %p\n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p\n", p+=x); $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4 a は int 型 10 個分のサイズ つまり sizeof(int[10]) p は int * 型 1 個分のサイズ つまり sizeof(int *) 配列 int p[N]; ポインタ int *p; sizeof(p) ○ 変数pの割り当てバイト数
51
1次元配列とポインタ 教科書pp.207-272. ⋮ pointer_ex5.c
配列変数aはメモリ上のどこかに確保されている a は配列全体を意味する(実際は先頭アドレス) &a は配列変数aのアドレス(aと同一) ポインタ変数pはメモリ上のどこかに確保されている &pはそのアドレス p はポインタ変数 p に格納されているアドレス この例では配列変数aの先頭アドレス&aまたはa *a, *p は各アドレス a, p に格納されたデータ 教科書pp 0x22aaa0 a[0] 0x22aaa4 a[1] 1 ⋮ 0x22aa98 p 1次元配列とポインタ pointer_ex5.c cygwin64 + mintty + bash + GNU C 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p\n", sizeof(a)); printf("sizeof(p) = %p\n", sizeof(p)); printf("&a = %p\n", &a); printf("&p = %p\n", &p); printf("a = %p\n", a); printf("p = %p\n", p); printf("*a = %d\n", *a); printf("*p = %d\n", *p); printf("a[x] = %d\n", a[x]); printf("p[x] = %d\n", p[x]); printf("*(a+x) = %d\n", *(a+x)); printf("*(p+x) = %d\n", *(p+x)); printf("&a[x] = %p\n", &a[x]); printf("&p[x] = %p\n", &p[x]); printf("a+x = %p\n", a+x); printf("p+x = %p\n", p+x); //printf("a+=x = %p\n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p\n", p+=x); $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4 配列 int p[N]; ポインタ int *p; &p △ ○ 変数pのアドレス(配列の場合&pとpは同じ) p アドレスp(p[0]のアドレス) *p アドレスpの格納値(p[0]の格納値)
52
1次元配列とポインタ 教科書pp.207-272. pointer_ex5.c
cygwin64 + mintty + bash + GNU C 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p\n", sizeof(a)); printf("sizeof(p) = %p\n", sizeof(p)); printf("&a = %p\n", &a); printf("&p = %p\n", &p); printf("a = %p\n", a); printf("p = %p\n", p); printf("*a = %d\n", *a); printf("*p = %d\n", *p); printf("a[x] = %d\n", a[x]); printf("p[x] = %d\n", p[x]); printf("*(a+x) = %d\n", *(a+x)); printf("*(p+x) = %d\n", *(p+x)); printf("&a[x] = %p\n", &a[x]); printf("&p[x] = %p\n", &p[x]); printf("a+x = %p\n", a+x); printf("p+x = %p\n", p+x); //printf("a+=x = %p\n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p\n", p+=x); $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4
53
1次元配列とポインタ 教科書pp.207-272. pointer_ex5.c
int p[N]; ポインタ int *p; p[x] ○ アドレスpを先頭にしてx個目の要素(p[x])の格納値 *(p+x) 1次元配列とポインタ pointer_ex5.c cygwin64 + mintty + bash + GNU C 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p\n", sizeof(a)); printf("sizeof(p) = %p\n", sizeof(p)); printf("&a = %p\n", &a); printf("&p = %p\n", &p); printf("a = %p\n", a); printf("p = %p\n", p); printf("*a = %d\n", *a); printf("*p = %d\n", *p); printf("a[x] = %d\n", a[x]); printf("p[x] = %d\n", p[x]); printf("*(a+x) = %d\n", *(a+x)); printf("*(p+x) = %d\n", *(p+x)); printf("&a[x] = %p\n", &a[x]); printf("&p[x] = %p\n", &p[x]); printf("a+x = %p\n", a+x); printf("p+x = %p\n", p+x); //printf("a+=x = %p\n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p\n", p+=x); $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4 a の x 番目の要素にアクセスしている
54
1次元配列とポインタ 教科書pp.207-272. pointer_ex5.c
int p[N]; ポインタ int *p; &p[x] ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス p+x p+=x × 1次元配列とポインタ pointer_ex5.c cygwin64 + mintty + bash + GNU C 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int x; int a[] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; fprintf(stderr, "x = ? "); scanf("%d", &x); printf("sizeof(a) = %p\n", sizeof(a)); printf("sizeof(p) = %p\n", sizeof(p)); printf("&a = %p\n", &a); printf("&p = %p\n", &p); printf("a = %p\n", a); printf("p = %p\n", p); printf("*a = %d\n", *a); printf("*p = %d\n", *p); printf("a[x] = %d\n", a[x]); printf("p[x] = %d\n", p[x]); printf("*(a+x) = %d\n", *(a+x)); printf("*(p+x) = %d\n", *(p+x)); printf("&a[x] = %p\n", &a[x]); printf("&p[x] = %p\n", &p[x]); printf("a+x = %p\n", a+x); printf("p+x = %p\n", p+x); //printf("a+=x = %p\n", a+=x); // Address of array variable can not be modified. printf("p+=x = %p\n", p+=x); $ gcc pointer_ex5.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 *a = 0 *p = 0 a[x] = 1 p[x] = 1 *(a+x) = 1 *(p+x) = 1 &a[x] = 0x22aaa4 &p[x] = 0x22aaa4 a+x = 0x22aaa4 p+x = 0x22aaa4 p+=x = 0x22aaa4 pに1を足しているのに 結果が4増えていることが 確認出来る
55
配列を引数に取る関数 関数定義の引数と配列
56
配列のサイズ sizeof で得られるバイト数 int a[N]; 講義資料 第3週p.42-51. : 0x?? a = &a[ -1]
sizeof(a[0]) = sizeof(int) 0x?? a + 1 = &a[ 1] 0x?? a + 2 = &a[ 2] sizeof(a) = sizeof(int) * N 0x?? : sizeof_ex3.c 0x?? : int a[10]; printf("sizeof(int) : %2d\n", sizeof(int)); //int型の割り当てバイト数 printf("sizeof(a) : %2d\n", sizeof(a)); //配列変数aの割り当てバイト数 printf("sizeof(a[0]) : %2d\n", sizeof(a[0])); //変数a[0]の割り当てバイト数 printf("sizeof(a)/sizeof(a[0]) : %2d\n", sizeof(a)/sizeof(a[0])); //配列変数aの要素数 0x?? a+N-1 = &a[N-1] 0x?? a+N = &a[N ] 0x?? a+N+1 = &a[N+1] : mintty + bash + GNU C $ gcc sizeof_ex3.c && ./a sizeof(int) : 4 sizeof(a) : 40 sizeof(a[0]) : 4 sizeof(a)/sizeof(a[0]) : 10 オレンジ色は 未割当のメモリ sizeof(int)
57
関数定義の引数と配列 引数名直後の1次元はポインタ扱いになる [1] pp.121-122. pointer_ex6.c
mintty + bash + GNU C 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 int main() { // int a[10] = { 2, 3, 5, 7,11,13,17,19,23,29}; int b[2][5] = { 2, 3, 5, 7,11,13,17,19,23,29}; printf("sizeof(a) = %d\n", sizeof(a)); func_with_array1d(a); func_with_pointer(a); printf("sizeof(b) = %d\n", sizeof(b)); func_with_array2d(b); func_with_pointer(b); //a += 1; // It's impossible. Because a is an array. //b += 1; // It's impossible. Because b is an array. return EXIT_SUCCESS; } $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 pointer_ex6.c 20 21 22 23 void func_with_pointer(int *p) { printf("sizeof(p) = %d\n", sizeof(p)); }
58
関数定義の引数と配列 引数名直後の1次元はポインタ扱いになる [1] pp.121-122. pointer_ex6.c
mintty + bash + GNU C 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 int main() { // int a[10] = { 2, 3, 5, 7,11,13,17,19,23,29}; int b[2][5] = { 2, 3, 5, 7,11,13,17,19,23,29}; printf("sizeof(a) = %d\n", sizeof(a)); func_with_array1d(a); func_with_pointer(a); printf("sizeof(b) = %d\n", sizeof(b)); func_with_array2d(b); func_with_pointer(b); //a += 1; // It's impossible. Because a is an array. //b += 1; // It's impossible. Because b is an array. return EXIT_SUCCESS; } $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 pointer_ex6.c 20 21 22 23 void func_with_pointer(int *p) { printf("sizeof(p) = %d\n", sizeof(p)); }
59
関数定義の引数と配列 引数名直後の1次元はポインタ扱いになる [1] pp.121-122. pointer_ex6.c
mintty + bash + GNU C 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 int main() { // int a[10] = { 2, 3, 5, 7,11,13,17,19,23,29}; int b[2][5] = { 2, 3, 5, 7,11,13,17,19,23,29}; printf("sizeof(a) = %d\n", sizeof(a)); func_with_array1d(a); func_with_pointer(a); printf("sizeof(b) = %d\n", sizeof(b)); func_with_array2d(b); func_with_pointer(b); //a += 1; // It's impossible. Because a is an array. //b += 1; // It's impossible. Because b is an array. return EXIT_SUCCESS; } $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 pointer_ex6.c 備考より関数の引数に与えた int b[2][5] が int (*)[5] 扱い されていることが分かる 20 21 22 23 void func_with_pointer(int *p) { printf("sizeof(p) = %d\n", sizeof(p)); }
60
関数定義の引数と配列 ポインタ扱いなので配列サイズが取れない [1] pp.121-122. pointer_ex6.c
1次元目はポインタになるので 配列サイズが取れない ポインタ扱いなので配列サイズが取れない pointer_ex6.c mintty + bash + GNU C 4 5 6 7 8 9 void func_with_array1d(int x[10]) { printf("sizeof(x) = %d\n", sizeof(x)); printf("sizeof(x[0]) = %d\n", sizeof(x[0])); x += 1; // x はポインタなので変更出来る } $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 sizeof(int[10]) ではなく sizeof(int*) pointer_ex6.c sizeof(int[2][5]) ではなく sizeof(int(*)[5]) 11 12 13 14 15 16 17 18 void func_with_array2d(int y[2][5]) { printf("sizeof(y) = %d\n", sizeof(y)); printf("sizeof(y[0]) = %d\n", sizeof(y[0])); printf("sizeof(y[0][0]) = %d\n", sizeof(y[0][0])); y += 1; // y はポインタなので変更出来る //y[0] += 1; // y[0] は配列なので変更来ない } 配列へのポインタなので 2次元目以降のサイズは取れる sizeof(y[0])は sizeof(int[5])
61
関数定義の引数と配列 ポインタ扱いなのでアドレスが変更出来る [1] pp.121-122. pointer_ex6.c
mintty + bash + GNU C 4 5 6 7 8 9 void func_with_array1d(int x[10]) { printf("sizeof(x) = %d\n", sizeof(x)); printf("sizeof(x[0]) = %d\n", sizeof(x[0])); x += 1; // x はポインタなので変更出来る } 定義は配列変数 int x[10] に見えるが 実はポインタ変数 int *p なので アドレスの変更出来る $ gcc pointer_ex6.c && ./a pointer_ex6.c: 関数 ‘main’ 内: pointer_ex6.c:37:21: 警告: 互換性のないポインタ型から 1 番目の ‘func_with_pointer’ の引数に渡しています func_with_pointer(b); ^ pointer_ex6.c:20:6: 備考: expected ‘int *’ but argument is of type ‘int (*)[5]’ void func_with_pointer(int *p) sizeof(a) = 40 sizeof(x) = 8 sizeof(x[0]) = 4 sizeof(p) = 8 sizeof(b) = 40 sizeof(y) = 8 sizeof(y[0]) = 20 sizeof(y[0][0]) = 4 pointer_ex6.c 定義は配列変数 int y[2][5]に見えるが 実はポインタ変数 int (*y)[5] なので アドレスの変更出来る 11 12 13 14 15 16 17 18 void func_with_array2d(int y[2][5]) { printf("sizeof(y) = %d\n", sizeof(y)); printf("sizeof(y[0]) = %d\n", sizeof(y[0])); printf("sizeof(y[0][0]) = %d\n", sizeof(y[0][0])); y += 1; // y はポインタなので変更出来る //y[0] += 1; // y[0] は配列なので変更来ない } y はポインタだが、 y[0] は int[5] 配列だから アドレスの変更が出来ない アンコメントして コンパイル出来ない事を確認せよ
62
確認: 関数定義の引数と配列 以下の個所を変えてコンパイルし確認せよ [1] pp.121-122. pointer_ex6.c
int x[10] ではなく int x[20] や int x[] にしても 問題なくコンパイル出来る事を確認せよ 4 5 6 7 8 9 void func_with_array1d(int x[10]) { printf("sizeof(x) = %d\n", sizeof(x)); printf("sizeof(x[0]) = %d\n", sizeof(x[0])); x += 1; // It's possible. Because x is pointer. } pointer_ex6.c int y[2][5] ではなく int y[20][5], int y[][5]にしても 問題なくコンパイル出来る事を確認せよ int y[2][10] だと コンパイル出来ないことを確認せよ 11 12 13 14 15 16 17 18 void func_with_array2d(int y[2][5]) { printf("sizeof(y) = %d\n", sizeof(y)); printf("sizeof(y[0]) = %d\n", sizeof(y[0])); printf("sizeof(y[0][0]) = %d\n", sizeof(y[0][0])); y += 1; // It's possible. Because y is a pointer to an array. //y[0] += 1; // It's impossible. Because y is an array. } 変数名に一番近い次元はポインタ扱いされるので 要素数が意味を持たなくなっていることを確認せよ
63
関数定義の引数と配列 引数名直後の1次元は要素数を省略すべき [1] pp.121-122. pointer_ex6.c
int x[10] という引数の宣言は int *x と同じ意味 4 5 6 7 8 9 void func_with_array1d(int x[10]) { printf("sizeof(x) = %d\n", sizeof(x)); printf("sizeof(x[0]) = %d\n", sizeof(x[0])); x += 1; // It's possible. Because x is pointer. } pointer_ex6.c int y[2][5] という引数の宣言は int (*y)[5] と同じ意味 11 12 13 14 15 16 17 18 void func_with_array2d(int y[2][5]) { printf("sizeof(y) = %d\n", sizeof(y)); printf("sizeof(y[0]) = %d\n", sizeof(y[0])); printf("sizeof(y[0][0]) = %d\n", sizeof(y[0][0])); y += 1; // It's possible. Because y is a pointer to an array. //y[0] += 1; // It's impossible. Because y is an array. } 無意味な数値は なるべく書くべきでない int *x int (*y)[5] と書くのが最も正確 int x[10] より int x[] int y[2][5] より int y[][5] と書く方が実態に合っている 簡易表記?
64
関数定義の引数と配列 関数定義の仮引数では以下の定義は同義 [1] pp.121-122. 1次元配列 2次元配列 3次元配列 ... =
int sub(char s[N]) { // ... } int sub(char s[M][N]) { // ... } int sub(char s[L][M][N]) { // ... } ... = = = int sub(char s[]) { // ... } int sub(char s[][N]) { // ... } int sub(char s[][M][N]) { // ... } ... = = = int sub(char *s) { // ... } int sub(char (*s)[N]) { // ... } int sub(char (*s)[M][N]) { // ... } ...
65
関数定義の引数と配列 関数定義の仮引数では以下の定義は同義 [1] pp.121-122. 関数の引数では 配列の最初の次元は
無視されてポインタ扱いになる 関数定義の仮引数では以下の定義は同義 1次元配列 2次元配列 3次元配列 int sub(char s[N]) { // ... } int sub(char s[M][N]) { // ... } int sub(char s[L][M][N]) { // ... } ... = = = int sub(char s[]) { // ... } int sub(char s[][N]) { // ... } int sub(char s[][M][N]) { // ... } ... = = = int sub(char *s) { // ... } int sub(char (*s)[N]) { // ... } int sub(char (*s)[M][N]) { // ... } ...
66
関数定義の引数と配列 関数定義の仮引数では以下の定義は同義 [1] pp.121-122. 1次元配列 2次元配列 3次元配列 ...
int sub(char s[N]) { // ... } int sub(char s[M][N]) { // ... } int sub(char s[L][M][N]) { // ... } ... 関数の引数では配列の最初の次元は無視されてポインタ扱いになる 配列っぽく書いても良いが実際にはポインタとして処理される = = = int sub(char s[]) { // ... } int sub(char s[][N]) { // ... } int sub(char s[][M][N]) { // ... } ... = = = int sub(char *s) { // ... } int sub(char (*s)[N]) { // ... } int sub(char (*s)[M][N]) { // ... } ...
67
* はどこに係っているのか? 複雑なポインタの宣言
68
演算子の優先度 優先度 高 低 [1] p.65. より 演算子 結合規則 備考 ( ) [ ] -> . 左から右→
( ) [ ] -> . 左から右→ ! ~ * & (type) sizeof 右から左← 単項演算子 * / % 二項演算子 + - << >> bitシフト < <= > >= 関係演算子 == != 等値演算子 & bit毎のAND ^ bit毎のXOR | bit毎のOR && 論理演算子(AND) || 論理演算子(OR) ?: 三項演算子 = += -= *= /= %= &= ^= |= <<= >>= 代入演算子 , 低 [1] p.65. より
69
複雑なポインタの宣言 配列はポインタ自身か?指している先か? 教科書.pp.235-239., [1]pp.148-153.
pointer_ex7.c int *(p1[7]); // int *p1[7]; と同義 int (*p2)[7]; [] は * よりも優先順位の高い演算子 int *(p1[7]); int a1; int (*p2)[7]; int a2[7]; 0x22aaa0 p1[0] 0x22aa98 0x22aaa8 p1[1] ? ⋮ 0x22aad0 p1[6] 0x22aa98 a1 0x22ab98 p2 0x22aaa0 0x22aba0 a2[0] 0x22aba4 a2[1] 1 ⋮ 0x22abbb a2[6] int *(p1[7]); → *(p1[x])がint int (*p2)[7]; → *p2がint[7]
70
複雑なポインタの宣言 * を付けると何になるか? 教科書.pp.235-239., [1]pp.148-153. pointer_ex7.c
mintty + bash + GNU C int *(p1[7]); // int *p1[7]; と同義 int (*p2)[7]; printf("sizeof( p1)=%2d\n", sizeof( p1)); printf("sizeof( p2)=%2d\n", sizeof( p2)); printf("sizeof(*p1)=%2d\n", sizeof(*p1)); printf("sizeof(*p2)=%2d\n", sizeof(*p2)); $ gcc pointer_ex7.c && ./a sizeof( p1)=56 sizeof( p2)= 8 sizeof(*p1)= 8 sizeof(*p2)=28 [] は * よりも優先順位の高い演算子 宣言の読み方 p1[x]に*が付く、つまり*p1[x]がint型になる 従ってp1[x]はint*型、つまりp1は要素数7のint*型配列 int *(p1[7]); → *(p1[x])がint p2に*が付く、つまり*p2が要素数7のint型配列になる 従ってp2は「『要素数7のint型配列』へのポインタ」 int (*p2)[7]; → *p2がint[7] 要注意
71
関数をポインタ変数に代入して呼び出す 関数へのポインタ
72
関数へのポインタ 関数へのポインタの例 pointer_ex8.c mintty + bash + GNU C 関数への
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int compi(const int *a, const int *b) { return *a - *b; } int main() int a, b; int (*fnc)(const int *a, const int *b) = compi; fprintf(stderr, "a = ? "); scanf("%d", &a); fprintf(stderr, "b = ? "); scanf("%d", &b); printf("(*fnc)(&a, &b) = %d\n", (*fnc)(&a, &b)); return EXIT_SUCCESS; $ gcc pointer_ex8.c && ./a a = ? 10 b = ? 20 (*fnc)(&a, &b) = -10 関数への ポインタ変数 fnc に関数 compi を 代入して fnc 経由で 関数 compi を 呼び出す
73
関数へのポインタ 作り方 例: 関数のプロトタイプ宣言を書き写す 関数名を変数名に書き変える 変数名を ( ) で囲む
変数名の前に * を付ける 例: 格納したい関数のプロトタイプ宣言 int compi(const int *a, const int *b); 関数へのポインタの宣言 int (*fnc)(const int *, const int *); 関数へのポインタによる関数の呼び出し (*fnc)(&a, &b); 引数名は省略可能 fnc という変数を宣言 戻り値が int int *型の引数を2つ取る 関数のアドレスを 格納出来る fnc という変数に 格納されたアドレスにある 関数に引数を渡して実行
74
関数へのポインタ ポインタ変数に * が付いたら何になるか? 関数の定義 cmp が 戻り値が int 型
引数が (int *a, int *b) の 関数という意味になる int cmp(int *a, int *b) { return *a - *b; } fnc がポインタ変数で *fnc が 戻り値が int 型 引数が (int *a, int *b) の 関数という意味になる 関数のプロトタイプ宣言 int cmp(int *a, int *b); 関数へのポインタ変数の宣言 int (*fnc)(int *a, int *b); *fnc とすれば 関数へのポインタが関数になる 演算子の優先順位は 高*>()低なので*fncに()が必要 関数へのポインタに代入と呼び出し fnc = cmp; (*fnc)(&x, &y);
75
関数へのポインタ 関数関連の宣言では引数名は省略可能 関数のプロトタイプ宣言 関数へのポインタ変数の宣言 関数のプロトタイプ宣言
int cmp(int *a, int *b); int cmp(int * , int * ); 関数へのポインタ変数の宣言 関数へのポインタ変数の宣言 int (*fnc)(int *a, int *b); int (*fnc)(int * , int * );
76
関数へのポインタ 汎用型の場合キャストが必要 例: qsort や bsearch へ渡す比較関数 関数のプロトタイプ宣言
int cmp(int *a, int *b); 関数へのポインタ変数の宣言 int (*fnc)(void *a, void *b); 関数へのポインタに代入と呼び出し fnc = (int (*)(void *, void *)) cmp; (*fnc)(&x, &y);
77
void 型と void 型へのポインタ void(=空洞)つまり大きさがない 関数に引数や戻り値がないことを意味する
ポインタの指し示す先の大きさが不明(特定の型に縛られない)であることを意味する 変数の宣言 void a; // void 型の変数はないのでコンパイルエラー void *p; // p が void 型へのポインタ 0x~0 0x~0 大きさが0バイトのデータ つまり void a; には意味がないが 大きさが不明のデータでも先頭アドレス つまり void *p; には意味がある 0x~1 0x~1 p= 0x~0 0x~2 0x~2 0x~3 0x~3
78
void 型と void 型へのポインタ void * 型は使用時に大きさを決めて使う 適当な型へのポインタとしてキャストする
int a; void *p = &a; // p に a のアドレスが入る *((int *)p) = 1; // p は void* なので *p は void だが // p を int* にキャストすると *p が int になる 0x~0 0x~0 *p は void なので意味がない 0x~1 0x~1 p= 0x~0 0x~2 0x~2 *((int*)p) は int なので意味がある 0x~3 0x~3
79
標準ライブラリの qsort 関数 要素サイズsize,要素数nのデータbaseを比較関数cmpの結果に従い並べ替える 引数 戻り値
[1] pp , 319. JM: qsort (3) 標準ライブラリの qsort 関数 void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *, const void *)); 要素サイズsize,要素数nのデータbaseを比較関数cmpの結果に従い並べ替える 引数 base: データへのポインタ n : データの要素数 size: 1要素当りのバイト数 cmp : 比較に用いる関数へのポインタ 戻り値 なし void * 型は 任意の方へのポインタ 並べ替えの順序 昇順、降順 データ型 int, double, 文字列 関数へのポインタ 並べ替えの際、 比較を可換にすることで 汎用性を持たせている。
80
qsort 利用の例 比較関数を用意すれば任意データに使える qsort_ex1.c int 型のデータの並べ替えの例
28 29 30 31 int compi(const int *a, const int *b) { return *a - *b; } int 型のデータの比較関数 qsort_ex1.c 47 48 49 printiv(v, n); qsort(v, n, sizeof(int), (int (*)(const void *, const void *)) compi); mintty + bash + GNU C const void * を 引数とする関数として キャストする必要がある $ gcc qsort_ex1.c && ./a n = ? 10 v[] = v[] =
81
qsort 利用の例 比較関数を用意すれば任意データに使える 配列へのポインタ並べ替えの例 qsort_ex2.c 標準ライブラリ関数
47 48 49 50 int strcmp_wrapper(const char **a, const char **b) { return strcmp(*a, *b); } 標準ライブラリ関数 strcmp のラッパー関数 qsort_ex2.c 67 68 69 printsv(v, n); qsort(v, n, sizeof(char*), (int (*)(const void*,const void*))strcmp_wrapper); mintty + bash + GNU C const void * を 引数とする関数として キャストする必要がある $ gcc qsort_ex2.c && ./a n = ? 2 v[0] = "yocchzcmwhmufrdvde" v[1] = "iipfziuhu" v[0] = "iipfziuhu" v[1] = "yocchzcmwhmufrdvde"
82
標準ライブラリの2分探索関数 ソート済み配列を二分探索(binary search)する 引数 戻り値 key : 検索したい値へのポインタ
教科書 pp JM: bsearch (3) 標準ライブラリの2分探索関数 void *bsearch(const void *key, const void *base, size_t n, size_t size, int (*cmp)(const void *keyval, const void *datum)); ソート済み配列を二分探索(binary search)する 引数 key : 検索したい値へのポインタ base : 検索対象のデータ集合へのポインタ n : データの要素数 size : データの1要素当りのバイト数 cmp : データ比較用の関数へのポインタ 戻り値 マッチした項目へのポインタを返す マッチした項目がない場合NULLを返す
83
標準ライブラリの2分探索関数 比較関数さえ用意すれば簡単に検索出来る 教科書 pp.198-202. bsearch_ex1.c
29 30 31 32 33 34 p = bsearch(&key, data, sizeof(data)/sizeof(int), sizeof(int), (int (*)(const void*, const void*))cmp); if (p) { printf("data[%d] = %d\n", p - data, *p); } else { printf("not found.d\n"); } bsearch_ex1.c bsearch_ex1.c 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { int data[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, }; int key; int *p; 4 5 6 7 int cmp(const int *keyval, const int *datum) { return *keyval - *datum; } mintty + bash + GNU C $ gcc bsearch_ex1.c && ./a key = ? 113 data[29] = 113
84
メモリの確保・解放とポインタによる配列 動的配列
85
プログラムの実行時に 配列のサイズを適宜に決める事が必要
動的配列 多くの問題では 配列のサイズを事前に (コンパイルの段階では) 決められない。 実行時、プログラムに 与えるデータや パラメータによって 配列のサイズは決まる。 = プログラムの実行時に 配列のサイズを適宜に決める事が必要 malloc, calloc, realloc, free 関数
86
malloc 関数 void *malloc(site_t size); 引数: 戻り値 動的にメモリを確保する
確保したメモリへのポインタを返す 確保したメモリの内容は初期化されない エラーの場合は NULL を返す JM: malloc (3)
87
calloc 関数 void *calloc(site_t nobj, site_t size); 引数: 戻り値 動的にメモリを確保する
確保したメモリへのポインタを返す 確保したメモリの内容は0で初期化される エラーの場合は NULL を返す JM: malloc (3)
88
realloc 関数 void *realloc(void *p, site_t size); 引数: 戻り値
動的に確保したメモリのサイズを変更する 引数: p : サイズを変更するメモリへのポインタ size : 変更後のバイト数 戻り値 確保したメモリへのポインタを返す サイズ変更前後で小さい方のサイズまでは 前の内容が維持される 増加した部分のメモリの内容は初期化されない エラーの場合は NULL を返す 元のブロックは維持される JM: malloc (3)
89
free 関数 void free(void *p); 引数: 動的に確保したメモリを解放する p : 解放するメモリへのポインタ
JM: malloc (3)
90
動的配列の基本 malloc で確保し free で解放する 配列 動的配列 #define N 10 // 要素数は定数 // ...
int a[N]; // 配列の確保 // 確保できないとコンパイルに失敗する a[i] = x; // 変数に対する操作 // 配列はスコープの終わりで // 自動的に開放される int N = 10; // 要素数は変数も可 // ... int *a = malloc(sizeof(int) * N); // メモリの確保 if (a == NULL) {// 確保できないと a に NULL が入る fprintf(stderr, "Error: malloc failed.\n"); exit(EXIT_FAILURE); // エラー処理が必要 } a[i] = x; // 変数に対する操作 free(a); // メモリの解放 // 動的配列は解放しないといつまでも確保され続ける
91
動的配列の基本 malloc で確保し free で解放する 2次元画像の例
unsigned char *img; // 画像用の動的配列 (unsigned char 型へのポインタ) int w, h; // 画像の縦横サイズ // 動的にサイズを決める fprintf(stderr, "w = ?"); scanf("%d", &w); fprintf(stderr, "h = ?"); scanf("%d", &h); if ((img = malloc(3 * w * h)) == NULL) { // メモリーの動的確保 fprintf(stderr, "Error: in %s line %d: malloc failed\n", __FILE__, __LINE__); exit(EXIT_FAILURE); } // ... // ここで確保したメモリーに対する処理を行う free(img); // 使い終わって不要になったメモリーの解放
92
動的配列の例 1週目の bmptest.c と同じグラデーション dynamic_array_ex1.c malloc で確保したメモリは
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void print_ppm(unsigned char *img, int w, int h, int q); int main() { unsigned char *img; int w, h, x, y; fprintf(stderr, "w = ? "); scanf("%d", &w); fprintf(stderr, "h = ? "); scanf("%d", &h); if ((img = malloc(3 * w * h)) == NULL) { fprintf(stderr, "Error: in %s line %d: malloc failed\n", __FILE__, __LINE__); exit(EXIT_FAILURE); } for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { img[3 * (w * y + x) + 0] = 255; // R img[3 * (w * y + x) + 1] = 255 * x / w; // G img[3 * (w * y + x) + 2] = 255 * y / h; // B print_ppm(img, w, h, UCHAR_MAX); free(img); return EXIT_SUCCESS; malloc で確保したメモリは 普通に1次元配列のように使える a.bmp mintty + bash + GNU C $ gcc dynamic_array_test.c print_ppm.c && ./a | convert - a.bmp w = ? 255 h = ? 255
93
動的配列によるカラー画像 1次元配列を3次元配列のように使う dynamic_array_ex1.c
unsigned char *img; を unsigned char img[h][w][3]; と同じように使う 18 19 20 21 22 23 24 for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { img[3 * (w * y + x) + 0] = 255; // R img[3 * (w * y + x) + 1] = 255 * x / w; // G img[3 * (w * y + x) + 2] = 255 * y / h; // B } img[3 * (w * y + x) + 0] x img 動的配列は1次元的にしか取れないため、 要素の位置は自分で計算する必要がある。 R G B ... : ⋱ y h w*3
94
テキスト形式PPM画像の書き出し PPMの書き出しを関数化してみる print_ppm.c 関数のプロトタイプ宣言
#include <stdio.h> void print_ppm(unsigned char *img, int w, int h, int q) { int c, x, y; printf("P3\n"); printf("%d %d\n", w, h); printf("%d\n", q); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { printf(" "); for (c = 0; c < 3; c++) { printf(" %3d", img[3 * (w * y + x) +c]); } printf("\n"); // 作った関数の本体(並括弧の中身)を取り除き // 末尾に ; (セミコロン)を付ける void print_ppm(unsigned char *img, int w, int h, int q); y, x, c のループで書き出す x c R G B ... : ⋱ y
95
テキスト形式PPM画像の書き出し PPMの書き出しを関数化してみる print_ppm.c 関数のプロトタイプ宣言 x c R ... :
#include <stdio.h> void print_ppm(unsigned char *img, int w, int h, int q) { int c, x, y; printf("P3\n"); printf("%d %d\n", w, h); printf("%d\n", q); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { printf(" "); for (c = 0; c < 3; c++) { printf(" %3d", img[3 * (w * y + x) +c]); } printf("\n"); // 作った関数の本体(並括弧の中身)を取り除き // 末尾に ; (セミコロン)を付ける void print_ppm(unsigned char *img, int w, int h, int q); x c R ... : ⋱ G ... : ⋱ B ... : ⋱ y
96
テキスト形式PPM画像の読み込み 読み込んだパラメータに応じて大きさを変更 read_pnm_p3_core.c
実用上は、これ+エラー処理が必要 詳細は read_pnm_p3.c 参照 unsigned char *read_pnm_p3(int *w, int *h, int *q) { char s[10]; unsigned char *img; int y, x, c; scanf("%9s%d%d%d", s, w, h, q); if ((img = calloc(*h * *w * 3, 1)) == NULL) { return NULL; } for (y = 0; y < *h; y++) { for (x = 0; x < *w; x++) { for (c = 0; c < 3; c++) { scanf("%d", &img[3 * (*w * y + x) + c]); return img; パラメータの読み込み パラメータに応じて 動的配列の確保 配列に格納する値の 読み込み 確保し値を読み込んだ 動的配列を返す
97
配列の添え字の範囲 0~N-1 をずらして使う 配列の添え字調整
98
添え字0~N-1の範囲を自由変更出来ないか?
ポインタの応用: 配列の添え字調整 C言語における配列の宣言 型名 変数名[要素数]; 例: 要素数N個でint型の配列a int a[N]; 利用可能な要素は a[0]~a[N-1] 添え字0~N-1の範囲を自由変更出来ないか? ↓ ポインタを利用すれば可能
99
ポインタの応用: 配列の添え字調整 1元配列とメモリ上の配置 ⋮ a[-1] a[ 0] a[ 1] a[ 2] a[ 3] ⋮ p[-1]
... 確保されたメモリ 確保されたメモリ外 p[0] が a のどこに対応するか 調整してやれば添え字の範囲を 自由に調整して使える = = = int a[3]; int *p; として p = &a[0] とした場合 p = &a[1] とした場合 p = &a[-1] とした場合 a[ 0]~a[ 2] を p[-1]~p[ 1] として使う a[ 0]~a[ 2] を p[ 1]~p[ 3] として使う
100
ポインタの応用: 配列の添え字調整 オフセットを与えてポインタを格納する array_offset_ex1.c
a[i] と p[i] の対応を 確認しましょう int a[3] = {2,3,5}; int *p; int i; for (i = 0; i < 3; i++) { printf("%p: a[% d] = %d\n", &a[i], i, a[i]); } p = a + 1; for (i = -1; i <= 1; i++) { printf("%p: p[% d] = %d\n", &p[i], i, p[i]); p = a - 1; for (i = 1; i <= 3; i++) { mintty + bash + GNU C $ gcc array_offset_ex1.c && ./a 0x23cb00: a[ 0] = 2 0x23cb04: a[ 1] = 3 0x23cb08: a[ 2] = 5 0x23cb00: p[-1] = 2 0x23cb04: p[ 0] = 3 0x23cb08: p[ 1] = 5 0x23cb00: p[ 1] = 2 0x23cb04: p[ 2] = 3 0x23cb08: p[ 3] = 5
101
ポインタの応用: 配列の添え字調整 オフセットを与えてポインタを格納する array_offset_ex1.c
p は a[1] のアドレスを指す つまり p[0] が a[1] 結果、以下の要素が対応する a[ 0], a[ 1], a[ 2] p[-1], p[ 0], p[ 1] int a[3] = {2,3,5}; int *p; int i; for (i = 0; i < 3; i++) { printf("%p: a[% d] = %d\n", &a[i], i, a[i]); } p = a + 1; for (i = -1; i <= 1; i++) { printf("%p: p[% d] = %d\n", &p[i], i, p[i]); p = a - 1; for (i = 1; i <= 3; i++) { mintty + bash + GNU C $ gcc array_offset_ex1.c && ./a 0x23cb00: a[ 0] = 2 0x23cb04: a[ 1] = 3 0x23cb08: a[ 2] = 5 0x23cb00: p[-1] = 2 0x23cb04: p[ 0] = 3 0x23cb08: p[ 1] = 5 0x23cb00: p[ 1] = 2 0x23cb04: p[ 2] = 3 0x23cb08: p[ 3] = 5
102
ポインタの応用: 配列の添え字調整 オフセットを与えてポインタを格納する array_offset_ex1.c
p は a[-1] のアドレスを指す つまり p[0] が a[-1] 結果、以下の要素が対応する a[ 0], a[ 1], a[ 2] p[ 1], p[ 2], p[ 3] int a[3] = {2,3,5}; int *p; int i; for (i = 0; i < 3; i++) { printf("%p: a[% d] = %d\n", &a[i], i, a[i]); } p = a + 1; for (i = -1; i <= 1; i++) { printf("%p: p[% d] = %d\n", &p[i], i, p[i]); p = a - 1; for (i = 1; i <= 3; i++) { mintty + bash + GNU C $ gcc array_offset_ex1.c && ./a 0x23cb00: a[ 0] = 2 0x23cb04: a[ 1] = 3 0x23cb08: a[ 2] = 5 0x23cb00: p[-1] = 2 0x23cb04: p[ 0] = 3 0x23cb08: p[ 1] = 5 0x23cb00: p[ 1] = 2 0x23cb04: p[ 2] = 3 0x23cb08: p[ 3] = 5
103
ポインタの応用: 配列の添え字調整 2次元配列の場合
多次元配列も 結局は1次元の メモリアドレスに 割り振られる 2次元配列とメモリ上の配置 ⋮ a[ 0][ 0] a[ 0][ 1] a[ 0][ 2] a[ 1][ 0] a[ 1][ 1] a[ 1][ 2] a[ 2][ 0] a[ 2][ 1] a[ 2][ 2] ⋮ a[ 0][ 0] a[ 0][ 1] a[ 0][ 2] a[ 0][ 3] a[ 0][ 4] a[ 0][ 5] a[ 0][ 6] a[ 0][ 7] a[ 8][ 8] ⋮ a[ 1][-3] a[ 1][-2] a[ 1][-1] a[ 1][ 0] a[ 1][ 1] a[ 1][ 2] a[ 1][ 3] a[ 1][ 4] a[ 1][ 5] ⋮ a[ 2][-6] a[ 2][-5] a[ 2][-4] a[ 2][-3] a[ 2][-2] a[ 2][-1] a[ 2][ 0] a[ 2][ 1] a[ 2][ 2] ... 確保されたメモリ 添え字の範囲外だが 確保されたメモリ内 確保されたメモリ外 = 添え字の範囲外でも メモリ上で連続であれば アクセスは可能
104
ポインタの応用: 配列の添え字調整 2次元配列の場合
2次元配列とポインタのオフセット ⋮ a[ 0][ 0] a[ 0][ 1] a[ 0][ 2] a[ 1][ 0] a[ 1][ 1] a[ 1][ 2] a[ 2][ 0] a[ 2][ 1] a[ 2][ 2] ⋮ (*p)[-1][-1] (*p)[-1][ 0] (*p)[-1][ 1] (*p)[ 0][-1] (*p)[ 0][ 0] (*p)[ 0][ 1] (*p)[ 1][-1] (*p)[ 1][ 0] (*p)[ 1][ 1] int [3][3] へのポインタ p を作り p が a[ 1][ 1] を指すよう調整すれば a[ 0][ 0]~ a[ 2][ 2] を (*p)[-1][-1]~(*p)[ 1][ 1] として使える! = int a[3][3]; int (*p)[3][3]=(int (*)[3][3])&a[1][1];
105
ポインタの応用: 配列の添え字調整 2次元配列の場合
2次元配列とポインタのオフセット p = a とした時の (*p)[-1][-1]~(*p)[1][1]の範囲 ⋱ ⋮ ⋰ ... a[-1][-1] a[-1][ 0] a[-1][ 1] a[-1][ 2] a[-1][ 3] a[ 0][-1] a[ 0][ 0] a[ 0][ 1] a[ 0][ 2] a[ 0][ 3] a[ 1][-1] a[ 1][ 0] a[ 1][ 1] a[ 1][ 2] a[ 1][ 3] a[ 2][-1] a[ 2][ 0] a[ 2][ 1] a[ 2][ 2] a[ 2][ 3] a[ 3][-2] a[ 3][ 0] a[ 3][ 1] a[ 3][ 2] a[ 3][ 3] ... 確保されたメモリ 添え字の範囲外だが確保されたメモリ内 確保されたメモリ外 p = &a[1][1] とした時の (*p)[-1][-1]~(*p)[1][1]の範囲
106
ポインタの応用: 配列の添え字調整 2次元配列の場合
オフセットを与えてポインタを格納する array_offset_ex2_1.c a[1][1]は int[3][3]型ではなく int型なのでキャストが必要 int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int (*p)[3][3] = (int (*)[3][3]) &a[1][1]; int x, y; printf("sizeof(a) = %d\n", sizeof(a)); printf("sizeof(*p) = %d\n", sizeof(*p)); for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { printf("%p: a[% d][% d] = %d\n", &a[y][x], y, x, a[y][x]); } for (y = -1; y <= 1; y++) { for (x = -1; x <= 1; x++) { printf("%p: (*p)[% d][% d] = %d\n", &(*p)[y][x], y, x, (*p)[y][x]); int [3][3] へのポインタは int (*)[3][3]
107
ポインタの応用: 配列の添え字調整 2次元配列の場合
オフセットを与えてポインタを格納する array_offset_ex2_2.c int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int (*p)[3] = (int (*)[3]) &a[1][1]; int x, y; printf("sizeof(a) = %d\n", sizeof(a)); printf("sizeof(*p) = %d\n", sizeof(*p)); for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { printf("%p: a[% d][% d] = %d\n", &a[y][x], y, x, a[y][x]); } for (y = -1; y <= 1; y++) { for (x = -1; x <= 1; x++) { printf("%p: p[% d][% d] = %d\n", &p[y][x], y, x, p[y][x]); int [3][3] へのポインタは int (*)[3][3] だが int (*)[3] でも格納可能 使う時は *p としなくて良いので 後者の方が使い易い? 1次元配列でも int [3] への ポインタではなく int 型への ポインタを 使っていた
108
ポインタの応用: 配列の添え字調整 2次元配列の場合
mintty + bash + GNU C mintty + bash + GNU C $ gcc array_offset_ex2_1.c && ./a sizeof(a) = 36 sizeof(*p) = 36 0x22aa90: a[ 0][ 0] = 2 0x22aa94: a[ 0][ 1] = 3 0x22aa98: a[ 0][ 2] = 5 0x22aa9c: a[ 1][ 0] = 7 0x22aaa0: a[ 1][ 1] = 11 0x22aaa4: a[ 1][ 2] = 13 0x22aaa8: a[ 2][ 0] = 17 0x22aaac: a[ 2][ 1] = 19 0x22aab0: a[ 2][ 2] = 23 0x22aa90: (*p)[-1][-1] = 2 0x22aa94: (*p)[-1][ 0] = 3 0x22aa98: (*p)[-1][ 1] = 5 0x22aa9c: (*p)[ 0][-1] = 7 0x22aaa0: (*p)[ 0][ 0] = 11 0x22aaa4: (*p)[ 0][ 1] = 13 0x22aaa8: (*p)[ 1][-1] = 17 0x22aaac: (*p)[ 1][ 0] = 19 0x22aab0: (*p)[ 1][ 1] = 23 $ gcc array_offset_ex2_2.c && ./a sizeof(a) = 36 sizeof(*p) = 12 0x22aa90: a[ 0][ 0] = 2 0x22aa94: a[ 0][ 1] = 3 0x22aa98: a[ 0][ 2] = 5 0x22aa9c: a[ 1][ 0] = 7 0x22aaa0: a[ 1][ 1] = 11 0x22aaa4: a[ 1][ 2] = 13 0x22aaa8: a[ 2][ 0] = 17 0x22aaac: a[ 2][ 1] = 19 0x22aab0: a[ 2][ 2] = 23 0x22aa90: p[-1][-1] = 2 0x22aa94: p[-1][ 0] = 3 0x22aa98: p[-1][ 1] = 5 0x22aa9c: p[ 0][-1] = 7 0x22aaa0: p[ 0][ 0] = 11 0x22aaa4: p[ 0][ 1] = 13 0x22aaa8: p[ 1][-1] = 17 0x22aaac: p[ 1][ 0] = 19 0x22aab0: p[ 1][ 1] = 23 sizeof(*p)は異なる int (*p)[3][3]; だと sizeof(*p) は sizeof(int[3][3]) int (*p)[3]; だと sizeof(int[3])
109
ポインタの応用: 配列の添え字調整 2次元配列の場合
配列のサイズが必要になるのが欠点 array_offset_ex2_2.c int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int (*p)[3] = (int (*)[3]) &a[1][1]; int x, y; printf("sizeof(a) = %d\n", sizeof(a)); printf("sizeof(*p) = %d\n", sizeof(*p)); for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { printf("%p: a[% d][% d] = %d\n", &a[y][x], y, x, a[y][x]); } for (y = -1; y <= 1; y++) { for (x = -1; x <= 1; x++) { printf("%p: p[% d][% d] = %d\n", &p[y][x], y, x, p[y][x]); 最後の次元以外は要素数が既知でないと 配列へのポインタを作れない つまり動的配列に対して使えない
110
ポインタの応用: 配列の添え字調整 2次元配列の場合
動的配列の場合は自力でアドレスを計算する array_offset_ex3.c int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int *p = &a[1][1], w = 3, h = 3; int x, y; for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { printf("%p: a[% d][% d] = %d\n", &a[y][x], y, x, a[y][x]); } for (y = -1; y <= 1; y++) { for (x = -1; x <= 1; x++) { printf("%p: p[w * % d + % d] = %d\n", &p[w * y + x], y, x, p[w * y + x]); ポインタを1次元配列として用いる 但しアドレスを計算し易いオフセットを あらかじめ設定しておく アドレスは自力で計算
111
確認問題 以下の状況で9行目の時点の *p と p の値を16進数で答えよ。 hoge.c mintty + bash + GNU C 6
7 8 9 int a = 0x ; int *p = &a; printf("&a = %p\n", &a); mintty + bash + GNU C $ gcc hoge.c && ./a &a = 0x22aac4
112
確認問題 int 型へのポインタ変数 a, b を宣言する場合正しいのは以下のうちどれか? hoge.c int a, b; // (1)
113
参考文献 [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
Similar presentations
© 2024 slidesplayer.net Inc.
All rights reserved.