67
C言語入門 第9週 プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ, 情報処理言語Ⅰ(実習を含む。) 1

プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

Page 1: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

C言語入門 第9週

プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,

情報処理言語Ⅰ(実習を含む。)

1

Page 2: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

関数

2

Page 3: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

関数

• 関数の定義の書式:

3

関数の定義 戻り値の型 関数名(引数の宣言, ...) { // 関数に行わせる処理 // ... return 戻り値; // 戻り値の型がvoidの場合は不要 }

関数の宣言 戻り値の型 関数名(引数の宣言, ...);

第4週資料pp.33-41,48-49.

関数の利用 変数名 = 関数名(引数, ...);

.h ファイルへ書き出す

.c ファイルへ書き出す

適宜呼び出す

Page 4: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

関数の引数(値渡し、参照渡し)

• 変数のスコープ(有効範囲)

4

scopetest.c int gl = 100; void sub(int lo) { { int lo = 400; printf("%-4s : %3d: gl=%d, lo=%d¥n", __func__, __LINE__, ++gl, ++lo); } printf("%-4s : %3d: gl=%d, lo=%d¥n", __func__, __LINE__, ++gl, ++lo); } int main() { int lo = 200; sub(300); printf("%-4s : %3d: gl=%d, lo=%d¥n", __func__, __LINE__, ++gl, ++lo); 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

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

関数の引数は呼び出し元とは別の変数になっていた

第4週資料pp.42-44.

Page 5: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

関数の引数(値渡し、参照渡し)

• 値渡し: 呼出し元の値のコピーを渡す

5

call_by_value.c void sub(int lo) { lo = 200; } int main() { int lo = 100; sub(lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }

8 9 10 11 12 13 14 15 16 17 18 19 20

引数で受け取った変数を変更しても 呼び出し元には反映されない

第4週資料pp.42-44. 教科書p.171.

mintty + bash + GNU C $ g++ call_by_value.c && ./a lo=100

Page 6: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

関数の引数(値渡し、参照渡し)

• 参照渡し: 呼出し元の値の格納場所を渡す

6

call_by_pointer.c void sub(int *lo) { *lo = 200; } int main() { int lo = 100; sub(&lo); printf("lo=%d¥n", lo); return EXIT_SUCCESS; }

8 9 10 11 12 13 14 15 16 17 18 19 20

scanf で見たことがある書き方! &: アドレス演算子

第4週資料pp.42-44. 教科書p.171.

引数で受け取った変数を変更すると 呼び出し元にも反映される

これは正確には ポインタ渡しと言う

変数loのアドレスを 渡している

mintty + bash + GNU C $ g++ call_by_pointer.c && ./a lo=200

Page 7: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

関数の引数(値渡し、参照渡し)

• C++における参照渡し

7

call_by_reference.cpp 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++

$ g++ call_by_reference.cpp && ./a lo=200

4 5 6 7 8 9 10 11 12 13 14 15 16

参考

引数で受け取った変数を変更すると 呼び出し元にも反映される

C++では 本物の参照渡しが 可能になった

C++の参照渡しでは アドレス演算子「&」が不要

値が変化することが 分かり難いという デメリットもある

Page 8: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数

• メモリ上のアドレスを指し示すための変数

• char * 型: char 型の変数へのポインタ

• int * 型: int 型の変数へのポインタ

• 要はアドレスを格納するための変数

• ポインタ型のサイズはアドレス空間のビット数

• 32ビットアドレス空間なら4バイト

• 64ビットアドレス空間なら8バイト

• sizeof(char *)もsizeof(int *)も同じ

8 教科書pp.207-272.

pointer: 指し示す者

Page 9: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

メモリの構成

• 1byte単位でアドレスが振られている

• つまり各アドレスには1byteの値を格納出来る

0x00 0x00000000

0x00 0x00000001

0x00 0x00000002

0x00 0x00000003

0x00 0xffffffff

: :

: :

0x00 0x0000000000000000

0x00 0x0000000000000001

0x00 0x0000000000000002

0x00 0x0000000000000003

0x00 0xffffffffffffffff

: :

: :

32bitのOSは32bitのアドレス空間 最大232Bytes=4GiB

64bitのOSは64bitのアドレス空間 最大264Bytes=16EiB

アドレス 格納値 アドレス 格納値

教科書 pp.52-56. 第2週資料 p.45.

ポインタ変数には これらの値が入る

Page 10: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数

• 例: 16bit short型little endianの場合

0x34 0x~00

0x12 0x~01

0x?? 0x~02

0x?? 0x~03

: :

: :

0x?? 0x~04

0x?? 0x~05

0x?? 0x~06

: :

: :

0x?? 0x~07

0x?? 0x~08

short a = 0x1234; short *p = &a;

a

0x1234

a

p に &a を入れておくと *p は a への 参照として機能する C言語ではこれを ポインタと言う

10

p

0x~00

&a

0x~00

&aは 変数aの メモリ上の 先頭アドレス

実際に存在している変数はpで中にはアドレスが格納されている

*pはアドレスpに 置かれた変数(ここではa) への参照

要はリンク みたいなもの

*p

0x1234

0x~00

宣言時に*を付けると ポインタ変数になる

教科書pp.207-272.

pは変数*pの アドレスを指し示す

Page 11: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数

• 変数が配置されたアドレスを扱うための変数

11

pointertest1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("a=%d¥n", a); return EXIT_SUCCESS; }

4 5 6 7 8 9 10 11 12 13 14 15

教科書pp.207-272.

変数の宣言で 変数名の前にポインタ宣言子「*」を付けると ポインタ変数になる。 この場合、 int 型の変数 *p を宣言した結果、 実際には int 型変数へのポインタである int * 型変数 p が宣言されると 理解しておくとスッキリする。

mintty + bash + GNU C $ gcc pointertest1.c && ./a a=2

Page 12: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数

• 変数が配置されたアドレスを扱うための変数

12

pointertest1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("a=%d¥n", a); return EXIT_SUCCESS; }

4 5 6 7 8 9 10 11 12 13 14 15

教科書pp.207-272.

mintty + bash + GNU C $ gcc pointertest1.c && ./a a=2

0x~

&a

a

1

p

0x~

アドレス演算子「&」を用いて int 型変数 a のアドレス &a を int 型の変数へのポインタ p に代入する

Page 13: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数

• 変数が配置されたアドレスを扱うための変数

13

pointertest1.c int main() { int a = 1; int *p; p = &a; *p = 2; printf("a=%d¥n", a); return EXIT_SUCCESS; }

4 5 6 7 8 9 10 11 12 13 14 15

教科書pp.207-272.

mintty + bash + GNU C $ gcc pointertest1.c && ./a a=2

int 型の変数へのポインタ p の前に 間接演算子「*」を付けると ポインタ変数 p が指し示すアドレスを int 型の変数としてアクセス出来る。 今 p に変数 a のアドレス &a が 入っているので、 *p に対するあらゆる操作は 変数 a に対する操作と同義となる。 実際 *p を書き変えることで a が 書き変えられていることが確認出来る。

0x~ a

2

p

0x~ *p

Page 14: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数の宣言と代入

• 「*」と「=」の使い方は要注意

• 同じ「*」だが宣言時とそれ以外で意味が異なる

14

宣言時 それ以外

*p pをポインタとして宣言 pが指すアドレスの格納値

*p=x pにxを代入 *pにxを代入

ポインタ変数pを宣言 (言い換えると、ある型の変数*pを宣言)し 宣言した変数pに アドレスxを代入する

ポインタ変数pが指すアドレス (言い換えると、ある型の変数*p)に 値xを代入する

要注意

*は間接演算子 indirection operator または間接参照演算子 dereference operator

教科書pp.207-272., [1] pp.250,268-269.

*はポインタ宣言子 pointer declarator

Page 15: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数の宣言と代入

• 「*」と「=」の使い方は要注意

15

pointertest2.cpp int a = 1; int *p1, b; int *p2 = &a; p1 = &a; printf("sizeof(p1)=%d¥n", sizeof(p1)); printf("sizeof(b)=%d¥n", sizeof(b)); 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);

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

教科書pp.207-272.

要注意

mintty + bash + GNU C $ gcc pointertest2.c && ./a sizeof(p1)=8 sizeof(b)=4 &a=0x000000000022aabc p1=0x000000000022aabc p2=0x000000000022aabc a=1 *p1=1 *p2=1

Page 16: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数の宣言と代入

• 「*」と「=」の使い方は要注意

16

pointertest2.cpp int a = 1; int *p1, b; int *p2 = &a; p1 = &a; printf("sizeof(p1)=%d¥n", sizeof(p1)); printf("sizeof(b)=%d¥n", sizeof(b)); 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);

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

mintty + bash + GNU C $ gcc pointertest2.c && ./a sizeof(p1)=8 sizeof(b)=4 &a=0x000000000022aabc p1=0x000000000022aabc p2=0x000000000022aabc a=1 *p1=1 *p2=1

宣言時は「*」が間接演算子ではなく ポインタ宣言子として働いている 宣言時に初期化すると *p2 ではなく p2 に &a が代入される点が紛らわしい 要注意

教科書pp.207-272.

p1 に &a を代入している

要注意

宣言時以外は*は関節演算子として働く ポインタの前に*が付くと、指し示す アドレスに格納されている値を操作する

Page 17: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数の宣言と代入

• 「*」と「=」の使い方は要注意

17

pointertest2.cpp int a = 1; int *p1, b; int *p2 = &a; p1 = &a; printf("sizeof(p1)=%d¥n", sizeof(p1)); printf("sizeof(b)=%d¥n", sizeof(b)); 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);

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

mintty + bash + GNU C $ gcc pointertest2.c && ./a sizeof(p1)=8 sizeof(b)=4 &a=0x000000000022aabc p1=0x000000000022aabc p2=0x000000000022aabc a=1 *p1=1 *p2=1

「int *」型の宣言ではなく、 「int」型の宣言である点も紛らわしい。 p1 はポインタ変数だが b は通常のint型変数 複数の変数を宣言する場合 変数名の前に ポインタ宣言子「*」を付けた変数だけが ポインタ変数になる 要注意

教科書pp.207-272.

要注意

Page 18: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタの表示

• %p

• void *;ポインタとして印字(処理系依存)

18

例 printf("&a=%#0*p¥n", sizeof(&a)*2+2, &a);

第3週資料pp.24-33.

mintty + bash + GNU C $ gcc hoge.c && ./a &a=0x000000000022aabc

#: 16進表示が0でない場合、先頭を0xにする 0: フィールド幅いっぱいに左側から0を詰める *: 最小フィールド幅を引数で指定 p: void *;ポインタとして印字

%*pによる最小フィールド幅指定 1バイト=16進数2桁 先頭の0xで更に2桁

Page 19: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

配列 int p[N];

ポインタ int *p;

sizeof(p) ○ ○ 変数pの割り当てバイト数

&p △ ○ 変数pのアドレス(配列はpと同じ)

p ○ ○ アドレスp(p[0]のアドレス)

*p ○ ○ アドレスpの格納値

p[x] ○ ○ アドレスpを先頭にしてx個目の要素の格納値

*(p+x) ○ ○ アドレスpを先頭にしてx個目の要素(p[x])の格納値

&p[x] ○ ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス

p+x ○ ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス

p+=x × ○ アドレスpを先頭にしてx個目の要素(p[x])のアドレス

1次元配列とポインタ

• 機能としてはほぼ同じ

• ポインタはアドレスを変更可能(少し柔軟)

19

配列は1要素のバイト数×要素数 ポインタはアドレス空間のビット数/8

教科書pp.207-272.

Page 20: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数とアドレス演算

• 例: 16bit short型little endianの場合

0x34 0x~00

0x12 0x~01

0x?? 0x~02

0x?? 0x~03

: :

: :

0x?? 0x~04

0x?? 0x~05

0x?? 0x~06

: :

: :

0x?? 0x~07

0x?? 0x~08

short a = 0x1234; short *pa = &a;

*pa

±1するとsizeof(*pa)単位で アドレスが増減する つまり short 型配列の 0要素目、1要素目、... という意味になる

20

pa

pa+1

pa+2

pa+3

*(pa+1)

*(pa+2)

*(pa+3)

要注意

教科書pp.207-272.

Page 21: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数とアドレス演算

• 例: 32bit int型little endianの場合

0x78 0x~00

0x56 0x~01

0x34 0x~02

0x12 0x~03

: :

: :

0x?? 0x~04

0x?? 0x~05

0x?? 0x~06

: :

: :

0x?? 0x~07

0x?? 0x~08

int a = 0x12345678; int *pa = &a;

*pa

±1するとsizeof(*pa)単位で アドレスが増減する つまり int 型配列の 0要素目、1要素目、... という意味になる

21

pa

pa+1

*(pa+1)

要注意

教科書pp.207-272.

Page 22: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数の配列的利用法

• 例: 16bit short型little endianの場合

0x34 0x~00

0x12 0x~01

0x?? 0x~02

0x?? 0x~03

: :

: :

0x?? 0x~04

0x?? 0x~05

0x?? 0x~06

: :

: :

0x?? 0x~07

0x?? 0x~08

short a = 0x1234; short *pa = &a;

pa[0]

配列同様[x]で先頭x個目の 要素にアクセス出来る 要素の頭に&を付けると アドレスが得られる

22

&pa[0]

&pa[1]

&pa[2]

&pa[3]

pa[1]

pa[2]

pa[3]

教科書pp.207-272.

Page 23: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数の配列的利用法

• 例: 32bit int型little endianの場合

0x78 0x~00

0x56 0x~01

0x34 0x~02

0x12 0x~03

: :

: :

0x?? 0x~04

0x?? 0x~05

0x?? 0x~06

: :

: :

0x?? 0x~07

0x?? 0x~08

int a = 0x12345678; int *pa = &a;

pa[0]

配列同様[x]で先頭x個目の 要素にアクセス出来る 要素の頭に&を付けると アドレスが得られる

23

&pa[0]

&pa[1]

pa[1]

教科書pp.207-272.

Page 24: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

1次元配列とポインタ

24

pointertest3.c 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);

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

cygwin64 + mintty + bash + GNU C $ gcc pointertest3.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

教科書pp.207-272.

pに1を足しているのに 結果が4増えていることが 確認出来る

Page 25: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

1次元配列とポインタ

25

pointertest3.c 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);

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

cygwin64 + mintty + bash + GNU C $ gcc pointertest3.c && ./a x = ? 1 sizeof(a) = 0x28 sizeof(p) = 0x8 &a = 0x22aaa0 &p = 0x22aa98 a = 0x22aaa0 p = 0x22aaa0 <以下略>

教科書pp.207-272.

配列変数aはメモリに配置されたアドレス 配列変数aのアドレス&aはつまりaと同じ ポインタ変数pは メモリ上のどこかに確保されており &pはそのアドレス pはそのアドレス&pに格納されたアドレス この例では&a *pはそのアドレスpに格納されたデータ

Page 26: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

1次元配列とポインタ

• 例: 32bitOSで8bit char型little endianの場合

0x00 0x~00

0x01 0x~01

0x02 0x~02

0x03 0x~03

: :

: :

0x00 0x~04

0x~~ 0x~05

0x~~ 0x~06

: :

: :

0x~~ 0x~07

0x?? 0x~08

char a[] = {0,1,2,3}; char *p = &a;

a

26

&a =a =p

&p

p

教科書pp.207-272.

配列変数aはメモリに配置されたアドレス 配列変数aのアドレス&aはつまりaと同じ ポインタ変数pは メモリ上のどこかに確保されており &pはそのアドレス pはそのアドレス&pに格納されたアドレス この例では&a *pはそのアドレスpに格納されたデータ

これ全体が配列aであり aはその先頭アドレス

Page 27: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

1次元配列とポインタ

• 例: 32bitOSで8bit char型little endianの場合

0x00 0x~00

0x01 0x~01

0x02 0x~02

0x03 0x~03

: :

: :

0x00 0x~04

0x~~ 0x~05

0x~~ 0x~06

: :

: :

0x~~ 0x~07

0x?? 0x~08

char a[] = {0,1,2,3}; char *p = &a;

a

27

&a =a =p

&p

p

教科書pp.207-272.

sizeof(p) は OSのアドレス空間のビット数/8 つまりsizeof(char *)

sizeof(a) は aの1要素辺りのバイト数×要素数 つまりsizeof(char)*N

Page 28: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数

28

pointertest4.c 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]); }

6 7 8 9 10 11 12 13 14 15 16 17 18

教科書pp.207-272.

Cygwin64+mintty+bash+GNU C $ gcc pointertest4.c && ./a &a[0]=0x000000000022aa90 p = ? 0x000000000022aa98 *p=0x12345678 a[0]=0000000000 a[1]=0x00000001 a[2]=0x12345678 a[3]=0x00000003 a[4]=0x00000004 a[5]=0x00000005 a[6]=0x00000006 a[7]=0x00000007 a[8]=0x00000008 a[9]=0x00000009

結果がどうなるかは別として アドレス演算子を使って 変数のアドレスを入れなくても 適当な値を入れても動く。

Page 29: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

ポインタ変数

29

pointertest4.c 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]); }

6 7 8 9 10 11 12 13 14 15 16 17 18

教科書pp.207-272.

Cygwin64+mintty+bash+GNU C $ gcc pointertest4.c && ./a &a[0]=0x000000000022aa90 p=0x000000000022aa92 *p=0x12345678 a[0]=0x56780000 a[1]=0x00001234 a[2]=0x00000002 a[3]=0x00000003 a[4]=0x00000004 a[5]=0x00000005 a[6]=0x00000006 a[7]=0x00000007 a[8]=0x00000008 a[9]=0x00000009

結果が正しいかどうかは別として 配列の途中のアドレスを ポインタに入れることも出来る

Page 30: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

動的配列

30

多くの問題では 配列のサイズを事前に (コンパイルの段階では) 決められない。

実行時、プログラムに 与えるデータや パラメータによって 配列のサイズは決まる。

プログラムの実行時に 配列のサイズを適宜に決める事が必要

malloc, calloc, realloc, free 関数

Page 31: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

malloc 関数

• void *malloc(site_t size);

• 動的にメモリを確保する

• 引数:

• size: 確保するメモリのバイト数

• 戻り値

• 確保したメモリへのポインタを返す

• 確保したメモリの内容は初期化されない

• エラーの場合は NULL を返す

31

JM: malloc (3)

Page 32: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

calloc 関数

• void *calloc(site_t nobj, site_t size); • 動的にメモリを確保する

• 引数: • nobj: メモリを確保する要素数 • size: 1要素辺りのバイト数

• 戻り値 • 確保したメモリへのポインタを返す • 確保したメモリの内容は0で初期化される • エラーの場合は NULL を返す

32

JM: malloc (3)

Page 33: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

realloc 関数

• void *realloc(void *p, site_t size); • 動的に確保したメモリのサイズを変更する

• 引数: • p: サイズを変更するメモリへのポインタ • size: 変更後のバイト数

• 戻り値 • 確保したメモリへのポインタを返す • サイズ変更前後で小さい方のサイズまでは

前の内容が維持される • 増加した部分のメモリの内容は初期化されない • エラーの場合は NULL を返す

• 元のブロックは維持される

33

JM: malloc (3)

Page 34: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

free 関数

• void free(void *p);

• 動的に確保したメモリを解放する

• 引数:

• p: 解放するメモリへのポインタ

34

JM: malloc (3)

Page 35: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

演習

関数の作成

35

Page 36: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

演習: is_leap_year~.c の関数化

• 第4週資料 p.41. の演習をしましょう

36

Page 37: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

演習: is_odd.c

• print_evenodd.c を参考に以下の関数を作りなさい

• int is_odd(int i); • 奇数かどうか判定する

• 引数 • i: 判定する整数

• 戻り値 • 奇数なら1、奇数でなければ0を返す

• is_odd_test.c と共にコンパイルして動作を確認すること

37

Cygwin64+mintty+bash+GNU C $ gcc is_odd_test.c is_odd.c && ./a i = ? 1 odd number

2014-06-20追加

Page 38: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

演習: is_even.c

• print_evenodd.c を参考に以下の関数を作りなさい

• int is_even(int i); • 偶数かどうか判定する

• 引数 • i: 判定する整数

• 戻り値 • 偶数なら1、偶数でなければ0を返す

• is_even_test.c と共にコンパイルして動作を確認すること

38

Cygwin64+mintty+bash+GNU C $ gcc is_even_test.c is_even.c && ./a i = ? 2 even number

2014-06-20追加

Page 39: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

演習: is_prime.c

• print_isprime.c を参考に以下の関数を作りなさい

• int is_prime(int i); • 素数かどうか判定する

• 引数 • i: 判定する整数

• 戻り値 • 素数なら1、素数でなければ0を返す

• is_prime_test.c と共にコンパイルして動作を確認すること

39

Cygwin64+mintty+bash+GNU C $ gcc is_prime_test.c is_prime.c && ./a i = ? 7 prime number

2014-06-20追加

Page 40: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

演習: swapi.c

• 以下の関数を作りなさい

• void swapi(int *a, int *b);

• 引数で与えた変数の値を交換する

• 引数

• a, b: 値を交換する変数へのポインタ

• swapi_test.c と共にコンパイルして動作を確認すること

40

Cygwin64+mintty+bash+GNU C $ gcc swapi_test.c swapi.c && ./a a = ? 1 b = ? 2 a = 2 b = 1

ヒント 第6週資料pp.43-44. 本資料 p.6. を参考にせよ

2014-06-20追加

Page 41: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

演習: ヘッダファイルの作成

• ここまでに作った is_odd, is_even, is_prime, swapi 関数のプロトタイプ宣言を myfunc.h というファイルにまとめよ。

• 第4週資料p.38.のis_leap_year_func.hを参考にせよ。

• include ガードの識別子は MYFUNC_H とせよ。

41

誤:3週資料 正:4週資料

Page 42: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい

42

• N以下の整数について既知の素数とその倍

数をふるい落とすことで素数を判定するアルゴリズム

2 3 4 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 30

31 32 33 34 35 36 37 38 39 40

41 42 43 44 45 46 47 48 49 50

51 52 53 54 55 56 57 58 59 60

61 62 63 64 65 66 67 68 69 70

71 72 73 74 75 76 77 78 79 80

81 82 83 84 85 86 87 88 89 90

91 92 93 94 95 96 97 98 99 100

Page 43: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい

43

• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥

(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ

3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る

4. 探索リストに残った数を 素数リストに移動

2 3 4 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 30

31 32 33 34 35 36 37 38 39 40

41 42 43 44 45 46 47 48 49 50

51 52 53 54 55 56 57 58 59 60

61 62 63 64 65 66 67 68 69 70

71 72 73 74 75 76 77 78 79 80

81 82 83 84 85 86 87 88 89 90

91 92 93 94 95 96 97 98 99 100

Page 44: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい

44

• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥

(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ

3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る

4. 探索リストに残った数を 素数リストに移動

2 3 4 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 30

31 32 33 34 35 36 37 38 39 40

41 42 43 44 45 46 47 48 49 50

51 52 53 54 55 56 57 58 59 60

61 62 63 64 65 66 67 68 69 70

71 72 73 74 75 76 77 78 79 80

81 82 83 84 85 86 87 88 89 90

91 92 93 94 95 96 97 98 99 100

探索リスト先頭の数は素数 先頭の2を素数と判定し 2の倍数を探索リストから削除する

Page 45: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい

45

• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥

(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ

3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る

4. 探索リストに残った数を 素数リストに移動

2 3 4 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 30

31 32 33 34 35 36 37 38 39 40

41 42 43 44 45 46 47 48 49 50

51 52 53 54 55 56 57 58 59 60

61 62 63 64 65 66 67 68 69 70

71 72 73 74 75 76 77 78 79 80

81 82 83 84 85 86 87 88 89 90

91 92 93 94 95 96 97 98 99 100

探索リスト先頭の数は素数 先頭の3を素数と判定し 3の倍数を探索リストから削除する

Page 46: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい

46

• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥

(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ

3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る

4. 探索リストに残った数を 素数リストに移動

2 3 4 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 30

31 32 33 34 35 36 37 38 39 40

41 42 43 44 45 46 47 48 49 50

51 52 53 54 55 56 57 58 59 60

61 62 63 64 65 66 67 68 69 70

71 72 73 74 75 76 77 78 79 80

81 82 83 84 85 86 87 88 89 90

91 92 93 94 95 96 97 98 99 100

探索リスト先頭の数は素数 先頭の5を素数と判定し 5の倍数を探索リストから削除する

Page 47: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい

47

• 𝑁以下の素数の探索 1. 2以上𝑁以下の整数を探索リストに入れる 2. 検索リストの先頭の数𝑥

(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ

3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る

4. 探索リストに残った数を 素数リストに移動

2 3 4 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 30

31 32 33 34 35 36 37 38 39 40

41 42 43 44 45 46 47 48 49 50

51 52 53 54 55 56 57 58 59 60

61 62 63 64 65 66 67 68 69 70

71 72 73 74 75 76 77 78 79 80

81 82 83 84 85 86 87 88 89 90

91 92 93 94 95 96 97 98 99 100

探索リスト先頭の数は素数 先頭の7を素数と判定し 7の倍数を探索リストから削除する

Page 48: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい

48

• 𝑁以下の素数の探索 1. 2以上𝑁以下の数を探索リストに入れる 2. 検索リストの先頭の数𝑥

(これは素数)が𝑥 ≤ 𝑁なら ステップ3 そうでなければステップ4へ

3. 𝑥を素数リストに移し 𝑥の倍数を探索リストから ふるい落とした後 ステップ2に戻る

4. 探索リストに残った数を 素数リストに移動

2 3 4 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 30

31 32 33 34 35 36 37 38 39 40

41 42 43 44 45 46 47 48 49 50

51 52 53 54 55 56 57 58 59 60

61 62 63 64 65 66 67 68 69 70

71 72 73 74 75 76 77 78 79 80

81 82 83 84 85 86 87 88 89 90

91 92 93 94 95 96 97 98 99 100

先頭の11は≤ 100でないので 探索リストに残った数を素数と判定

Page 49: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 通常の配列による探索リスト

• メモリ上のイメージ

2 0

3 1

4 2

5 3

6 4

7 5

8 6

添字

: :

100 98

49

探索リストの初期化 #define N 100 int i, j = 2, n = N; // n は探索リストの件数 int slist[N-2]; for (i = 0; i < n; i++) { slist[i] = j++; }

n-1

訂正2014-06-20 誤:n = N 正:n = N - 2

訂正2014-06-20 誤:slist[N-1] 正:slist[N-2]

Page 50: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

100 98

エラトステネスのふるい 通常の配列による探索リスト

• 値の削除

2 0

3 1

4 2

5 3

6 4

7 5

8 6

添字

: :

100 98

50

n-1

2 0

3 1

5 2

6 3

7 4

8 5

: :

添字

100 97 n-1

4を削除

普通の配列だと 値を削除して 詰める処理に 時間がかかる

Page 51: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 通常の配列による探索リスト

51

• 探索リストの消費メモリ

• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N-1)*4バイト必要

探索リストの初期化 #define N 100 int i, j = 2, n = N-2; // n は探索リストの件数 int slist[N-2]; for (i = 0; i < n; i++) { slist[i] = j++; }

探索リストから値xの削除 for (i = 0; i < n; i++) { if (slist[i] == x) { for (j = 0; i + j + 1 < n; j++) { slist[i + j] = slist[i + j + 1]; // ↑ 探索リストから x を削除し // ↑ 以降の値を1つずつずらして詰める } n--; // 探索リストの件数をxを削除した分減らす break; } }

普通の配列だと 値を削除して 詰める処理に 時間がかかる

訂正あり p.49.参照

Page 52: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 通常の配列による探索リスト

52

• 探索リストの消費メモリ

• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N-1)*4バイト必要

探索リストで値xの判定 for (i = 0; i < n; i++) { if (slist[i] == x) { // x が探索リストにある場合の処理 break; } }

Page 53: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 通常の配列による探索リスト

• 値の削除を工夫

2 0

3 1

4 2

5 3

6 4

7 5

8 6

添字

: :

100 98

53

n-1

4を削除

使ってない値を マイナスにして 検索時に 無視すると 削除は速くなるが 値の検索でごみ になった-1も 処理しなければ いけなくなる

2 0

3 1

-1 2

5 3

6 4

7 5

8 6

添字

: :

100 98 n-1

マイナス値も 扱う場合は 使えない方法

Page 54: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 通常の配列による探索リスト

54

• 探索リストの消費メモリ

• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N-1)*4バイト必要

探索リストの初期化 #define N 100 int i, j = 2, n = N-2; // n は探索リストの件数 int slist[N-2]; for (i = 0; i < n; i++) { slist[i] = j++; }

探索リストから値xの削除 for (i = 0; i < n; i++) { if (slist[i] == x) { slist[i] = -1; // ↑ 探索リストから x を削除 break; } }

削除は速いが 検索時にゴミも 検索するので 検索が遅くなる

訂正2014-06-20 誤:slist[i+j] 正:slist[i]

訂正あり p.49.参照

Page 55: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 通常の配列による探索リスト

55

• 探索リストの消費メモリ

• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N-1)*4バイト必要

探索リストで値xの判定 for (i = 0; i < n; i++) { if (slist[i] == x) { // x が探索リストにある場合の処理 break; } }

Page 56: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 1方向リストによる探索リスト

• メモリ上のイメージ

2 [0][0]

1 [0][1]

3 [1][0]

2 [1][1]

4 [2][0]

3 [2][1]

5 [3][0]

添字

: :

99 [98][1]

56

N-1

探索リストの初期化 #define N 100 int i, j = 2; int slist[N-1][2]; for (i = 0; i < N - 2; i++) { slist[i][0] = j++; slist[i][1] = i+1; // 次の値の格納位置 } slist[i][1] = -1; // 終端記号

? [99][0]

-1 [99][1]

2つの値をペアとして扱い 1つ目を次の値の格納場所の添え字 2つ目を格納値 として利用している

1つ目の値を ポインターっぽく 使っている

マイナスの値を次の値がない目印 つまり終端記号として用いている

訂正2014-06-20 誤:slist[N][2] 正:slist[N-1][2]

訂正2014-06-20 誤:i<N-1でしたが 正:i<N-2の誤りでした

訂正2014-06-20 誤:slist[i][0] 正:slist[i][1]

Page 57: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 1方向リストによる探索リスト

• 値の削除

2 [0][0]

1 [0][1]

3 [1][0]

2 [1][1]

5 [2][0]

4 [2][1]

5 [3][0]

添字

: :

99 [98][1]

57

N-1

? [99][0]

-1 [99][1]

2 3 4

5 100 ? ×

2 3 5

5 100 ? ×

4を削除

Page 58: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 1方向リストによる探索リスト

58

• 探索リストの消費メモリ

• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N)*4*2バイト必要

探索リストの初期化 #define N 100 int i, j = 2; int slist[N-1][2]; for (i = 0; i < N - 2; i++) { slist[i][0] = j++; slist[i][1] = i+1; // 次の値の格納位置 } slist[i][0] = -1; // 終端記号

探索リストから値xの削除 for (i = 0; 0 <= slist[i][1]; i=slist[i][1]) { if (slist[i][0] == x) { j = slist[i][1]; slist[i][0] = slist[j][0]; slist[i][1] = slist[j][1]; // ↑ 探索リストの x が格納されている要素を // ↑ 次の要素で上書き break; } }

使わなくなった メモリは 無駄になるが 削除が高速

メモリが通常の配列の 倍必要

訂正あり p.56.参照

Page 59: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい 1方向リストによる探索リスト

59

• 探索リストの消費メモリ

• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N)*4*2バイト必要

探索リストで値xの判定 for (i = 0; 0 <= slist[i][1]; i=slist[i][1]) { if (slist[i][0] == x) { // x が探索リストにある場合の処理 break; } }

Page 60: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい フラグ配列による探索リスト(int版)

• メモリ上のイメージ

0 0

0 1

0 2

0 3

0 4

0 5

0 6

添字

: :

0 100

60

探索リストの初期化 #define N 100 int slist[N+1] = {0};

N

添え字をxとして考え 格納値がゼロなら有効 格納値が非ゼロなら無効 と考える

Page 61: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい フラグ配列による探索リスト(int版)

• 値の削除

0 0

0 1

0 2

0 3

0 4

0 5

0 6

添字

: :

0 100

61

N

4を削除 使ってない値を 非ゼロ 削除は速い 検索はごみも 検索するので あまり速くない

0 0

0 1

0 2

0 3

1 4

0 5

0 6

添字

: :

0 100 N

Page 62: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい フラグ配列による探索リスト(int版)

62

• 探索リストの消費メモリ

• 2以上N以下の数を配列に保存すると Nが32bitで表現可能な整数と仮定して 初期状態で(N+1)*4バイト必要

探索リストの初期化 #define N 100 int slist[N] = {0};

探索リストから値xの削除 slist[x] = 1; 検索しなくて良いので

削除は高速

こんなに必要?

探索リストで値xの判定 if (!slist[x]) { // x が 0 の時の処理 // x が探索リストにある場合の処理 }

Page 63: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい フラグ配列による探索リスト(char版)

63

• 探索リストの消費メモリ

• 2以上N以下の数を配列に保存すると 初期状態で(N+1)バイト必要

探索リストの初期化 #define N 100 char slist[N] = {0};

探索リストから値xの削除 slist[x] = 1; 検索しなくて良いので

削除は高速

char で十分 必要メモリが 1/4になった もっと減 らせるのでは?

探索リストで値xの判定 if (!slist[x]) { // x が 0 の時の処理 // x が探索リストにある場合の処理 }

Page 64: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

エラトステネスのふるい フラグ配列による探索リスト(1bit版)

64

• 探索リストの消費メモリ

• 2以上N以下の数を配列に保存すると 初期状態で(N+8)/8バイト必要

探索リストの初期化 #define N 100 char slist[(N+8)/8] = {0};

探索リストから値xの削除 slist[x/8] |= 1<<(x%8); 検索しなくて良いので

削除は高速

1bitでも十分 charの場合から 更に1/8になった

bit毎のOR演算による bitマスクを用いて 狙ったビットをONにする 探索リストで値xの判定

if (!(slist[x/8] & (1<<(x%8)))) { // x が 0 の時の処理 // x が探索リストにある場合の処理 }

bit毎のAND演算による bitマスクを用いて 狙ったビットのON/OFFを調べる

訂正2014-06-20 誤:slist[x/7] 正:slist[x/8]

Page 65: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

演習: print_prime_lt~.c

• エラストテネスのふるいによりN未満の素数を小さい順に全て表示せよ • print_prime_lt.c を元に以下のファイル名で作成せよ • print_prime_lt_a1.c

• 通常の配列版(削除した値を詰めた場合)

• print_prime_lt_a2.c • 通常の配列版(削除値を-1にした場合)

• print_prime_lt_b.c • 一方向リスト版

• print_prime_lt_c1.c • フラグ配列int版

• print_prime_lt_c2.c • フラグ配列char版

• print_prime_lt_c3.c • フラグ配列1bit版

65

Page 66: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

演習: print_prime_lt~.c

• 前頁で作成したプログラムについて速度を比較してみましょう

• Cygwin の場合 time コマンドを用いると実行実感が計測出来ます。例えば ./a の実行時間を計測するには以下のようにします。

66

mintty+bash+GNU C $ time ./a

Page 67: プログラミング言語Ⅰ(実習を含む。),web.cc.yamaguchi-u.ac.jp/~okadalab/CLangI2014/CLangI2014_9p.pdf · •参照渡し: 呼出し元の値の格納場所を渡す

参考文献

• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)

67