1 C言語入門 第12週 プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ, 情報処理言語Ⅰ(実習を含む。) 2 動的配列 講義資料 第6週p.59, 第9週pp.30-34. 動的配列の基本 • malloc で確保し free で解放する unsigned char *img; int w, h; // 動的にサイズを決める fprintf(stderr, "w = ?"); scanf("%d", &w); fprintf(stderr, "h = ?"); scanf("%d", &h); if ((img = malloc(3 * w * h)) == NULL) { // img の動的確保 fprintf(stderr, "Error: in %s line %d: malloc failed\n", __FILE__, __LINE__); exit(EXIT_FAILURE); } // ここで img に対する処理を行う free(img); // 使い終わったimgの解放 3 4 講義資料 第6週p.59, 第9週pp.30-34. 動的配列の例 • 1週目の bmptest.c と同じグラデーション dynamic_array_test.c 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); } malloc で確保したメモリは 普通に1次元配列のように使える for (y = 0; y < h; y++) { for (x = 0; x < w; x++) img[3 * (w * y + x) + img[3 * (w * y + x) + img[3 * (w * y + x) + } } { 0] = 255; // R 1] = 255 * x / w; // G 2] = 255 * y / h; // B print_ppm(img, w, h, UCHAR_MAX); free(img); return EXIT_SUCCESS; } a.bmp mintty + bash + GNU C $ gcc dynamic_array_test.c print_ppm.c && ./a | convert - a.bmp w = ? 255 h = ? 255 5 講義資料 第6週pp.10-30,57-59. 動的配列によるカラー画像 • 1次元配列を3次元配列のように使う dynamic_array_test.c 18 19 20 21 22 23 24 for (y = 0; y < h; y++) { for (x = 0; x < w; x++) img[3 * (w * y + x) + img[3 * (w * y + x) + img[3 * (w * y + x) + } } unsigned char *img; を unsigned char img[h][w][3]; と同じように使う { 0] = 255; // R 1] = 255 * x / w; // G 2] = 255 * y / h; // B img[3 * (w * y + x) + 0] x img R G B R G B ... R G B R G B R G B ... R G B y R G B R G B ... R G B : : : : : : ⋱ : : : R G B R G B ... R G B w*3 h 6 講義資料 第6週pp.47-53. テキスト形式 PPM 画像の書き出し • 第6週p.53.のプログラムを関数化した例 print_ppm.c 関数のプロトタイプ宣言 #include <stdio.h> // 作った関数の本体(並括弧の中身)を取り除き // 末尾に ; (セミコロン)を付ける void print_ppm(unsigned char *img, int w, int h, int q); 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"); } } x c R G B R G B ... R G B R G B R G B ... R G B y R G B R G B ... R G B : : : : : : ⋱ : : : R G B R G B ... R G B 7 講義資料 第6週pp.47-53. テキスト形式 PPM 画像の読み込み • 読み込んだパラメータに応じて大きさを変更 read_ppm_p3_core.c unsigned char *read_pnm_p3(int *w, int *h, int *q) { char s[10]; unsigned char *img; int y, x, c; 実用上は、これ+エラー処理が必要 詳細は read_ppm_p3.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; 確保し値を読み込んだ 動的配列を返す 8 ポインタの応用: 配列の添え字調整 C言語における配列の宣言 型名 変数名[要素数]; 例: 要素数N個でint型の配列a int a[N]; 利用可能な要素は a[0]~a[N-1] 添え字0~N-1の範囲を自由変更出来ないか? ↓ ポインタを利用すれば可能 9 ポインタの応用: 配列の添え字調整 • 1元配列とメモリ上の配置 ⋮ ⋮ ⋮ ... 確保されたメモリ a[-1] p[-1] p[-2] ... 確保されたメモリ外 a[ 0] p[ 0] p[-1] a[ 1] = p[ 1] = p[ 0] a[ 2] p[ 2] p[ 1] a[ 3] p[ 3] p[ 2] ⋮ ⋮ ⋮ int a[3]; int *p; として p = &a[0] とした場合 p = &a[1] とした場合 p[0] が a のどこに対応するか 調整してやれば良い a[ 0]~a[ 2] を p[-1]~p[ 1] として使える 10 ポインタの応用: 配列の添え字調整 • オフセットを与えてポインタを格納する array_offset_test1.c int a[3] = {2,3,5}; int *p = a + 1; int i; for (i = 0; i < 3; i++) { printf("%p: a[% d] = %d\n", &a[i], i, a[i]); } for (i = -1; i <= 1; i++) { printf("%p: p[% d] = %d\n", &p[i], i, p[i]); } a に対してオフセットを 1 加える p は a[1] のアドレスを指す つまり p[0] が a[1] 結果、以下の要素が対応する a[ 0], a[ 1], a[ 2] p[-1], p[ 0], p[ 1] mintty + bash + GNU C $ gcc array_offset_test1.c && ./a 0x22aab0: a[ 0] = 2 0x22aab4: a[ 1] = 3 0x22aab8: a[ 2] = 5 0x22aab0: p[-1] = 2 0x22aab4: p[ 0] = 3 0x22aab8: p[ 1] = 5 11 ポインタの応用: 配列の添え字調整 2次元配列の場合 多次元配列も 結局は1次元の メモリアドレスに 割り振られる • 2次元配列とメモリ上の配置 ⋮ ⋮ ⋮ ⋮ a[ 0][ 0] a[ 0][ 0] a[ 1][-3] a[ 2][-6] a[ 0][ 1] a[ 0][ 1] a[ 1][-2] a[ 2][-5] a[ 0][ 2] a[ 0][ 2] a[ 1][-1] a[ 2][-4] a[ 1][ 0] a[ 0][ 3] a[ 1][ 0] a[ 2][-3] a[ 1][ 1] = a[ 0][ 4] a[ 1][ 1] a[ 2][-2] a[ 1][ 2] a[ 0][ 5] a[ 1][ 2] a[ 2][-1] a[ 2][ 0] a[ 0][ 6] a[ 1][ 3] a[ 2][ 0] a[ 2][ 1] a[ 0][ 7] a[ 1][ 4] a[ 2][ 1] a[ 2][ 2] a[ 8][ 8] a[ 1][ 5] a[ 2][ 2] ⋮ ⋮ ⋮ ⋮ ... 確保されたメモリ ... 添え字の範囲外だが 確保されたメモリ内 ... 確保されたメモリ外 添え字の範囲外でも メモリ上で連続であれば アクセスは可能 12 ポインタの応用: 配列の添え字調整 2次元配列の場合 • 2次元配列とポインタのオフセット ⋮ ⋮ a[ 0][ 0] (*p)[-1][-1] a[ 0][ 1] (*p)[-1][ 0] a[ 0][ 2] (*p)[-1][ 1] a[ 1][ 0] (*p)[ 0][-1] a[ 1][ 1] = (*p)[ 0][ 0] a[ 1][ 2] (*p)[ 0][ 1] a[ 2][ 0] (*p)[ 1][-1] a[ 2][ 1] (*p)[ 1][ 0] a[ 2][ 2] (*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]; 13 ポインタの応用: 配列の添え字調整 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]の範囲 14 ポインタの応用: 配列の添え字調整 2次元配列の場合 • オフセットを与えてポインタを格納する array_offset_test2_1.c a[1][1]は int[3][3]型ではなく int型なのでキャストが必要 int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int [3][3] へのポインタは int (*p)[3][3] = (int (*)[3][3]) &a[1][1]; int (*)[3][3] 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]); } } 15 ポインタの応用: 配列の添え字調整 2次元配列の場合 • オフセットを与えてポインタを格納する array_offset_test2_2.c int a[3][3] = { { 2, 3, 5}, { 7,11,13}, int [3][3] へのポインタは {17,19,23}, int (*)[3][3] だが }; int (*)[3] でも格納可能 int (*p)[3] = (int (*)[3]) &a[1][1]; int x, y; 使う時は *p としなくて良いので 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]); 1次元配列でも } } int [3] への for (y = -1; y <= 1; y++) { ポインタではなく for (x = -1; x <= 1; x++) { int 型への printf("%p: p[% d][% d] = %d\n", &p[y][x], y, x, p[y][x]); ポインタを } 使っていた } 16 ポインタの応用: 配列の添え字調整 2次元配列の場合 mintty + bash + GNU C mintty + bash + GNU C $ gcc array_offset_test2_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_test2_2.c && ./a sizeof(a) = 36 sizeof(*p) = 12 sizeof(*p)は異なる 0x22aa90: a[ 0][ 0] = 2 0x22aa94: a[ 0][ 1] = 3 int (*p)[3][3]; だと 0x22aa98: a[ 0][ 2] = 5 sizeof(*p) は 0x22aa9c: a[ 1][ 0] = 7 sizeof(int[3][3]) 0x22aaa0: a[ 1][ 1] = 11 0x22aaa4: a[ 1][ 2] = 13 0x22aaa8: a[ 2][ 0] = 17 int (*p)[3]; だと 0x22aaac: a[ 2][ 1] = 19 sizeof(*p) は 0x22aab0: a[ 2][ 2] = 23 sizeof(int[3]) 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 17 ポインタの応用: 配列の添え字調整 2次元配列の場合 • 配列のサイズが必要になるのが欠点 array_offset_test2_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]); } } 18 ポインタの応用: 配列の添え字調整 2次元配列の場合 • 動的配列の場合は自力でアドレスを計算する array_offset_test3.c int a[3][3] = { { 2, 3, 5}, { 7,11,13}, {17,19,23}, }; int *p = &a[1][1], w = 3, h int x, y; for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { printf("%p: a[% d][% d] } } for (y = -1; y <= 1; y++) { for (x = -1; x <= 1; x++) printf("%p: p[w * % d + } } = 3; ポインタを1次元配列として用いる 但しアドレスを計算し易いオフセットを あらかじめ設定しておく = %d\n", &a[y][x], y, x, a[y][x]); アドレスは自力で計算 { % d] = %d\n", &p[w * y + x], y, x, p[w * y + x]); 19 第9週講義資料p.16. 確認問題 • 以下の状況で9行目の時点の *p と p の値を 16進数で答えよ。 hoge.c 6 int a = 0x12345678; 7 int *p = &a; 8 9 printf("&a = %p\n", &a); mintty + bash + GNU C $ gcc hoge.c && ./a &a = 0x22aac4 20 第9週講義資料p.17. 確認問題 • int 型へのポインタ変数 a, b を宣言する場合 正しいのは以下のうちどれか? hoge.c int a, b; // int *a, b; // int a, *b; // int *a, *b; // (1) (2) (3) (4) 21 演習: strtosign.c • 文字列sの先頭1文字を見て、「-」なら-1、「+」なら+1、そ れ以外なら+1を返す関数 strtosign を作成せよ • int strtosign(const char *s, char **endp); • 引数 • s: 文字列 • endp: NULL 以外の時、*endp に符号の識別子(つまり'','+')の次の文字へのポインタを返す • 戻り値 • 文字列sの先頭2文字に応じて、2、8、10、16の何れかを返す。 • endp が NULL 以外の時、*endp に符号の識別子(つまり '-','+')の次の文字へのポインタを返す • strtosign_test.c と共にコンパイルして動作を確認せ よ 第13週へ移動+改訂 22 演習: strtobase.c • 文字列sの先頭2文字を見て、0なら8進数、0bなら2進数、0xなら 16進数、それ以外なら10進数と判別する関数 strtobase を作 成せよ • int strtobase(const char *s, char **endp); • 引数 • s: 文字列 • endp: NULL 以外の時、*endp に基数判別の識別子(0, 0b, 0x)の次の文字へのポインタを返す • 戻り値 • 文字列sの先頭2文字に応じて、2、8、10、16の何れかを返す。 • endp が NULL 以外の時、*endp に基数判別の識別子(0, 0b, 0x)の次の文字へのポインタを返す • strtobase_test.c と共にコンパイルして動作を確認せよ 第13週へ移動+改訂 23 参考文献 • [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準 拠、共立出版(1989)
© Copyright 2024 ExpyDoc