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