C言語入門

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)