ソフトウェア工学入門 第 4 回ライブラリ関数
ライブラリ関数 stdio stdio : 標準入出力ライブラリ カーネルレベルのストリームに API を追加し インタフェースを提供する カーネル fd read(2) write(2) stdio バッファ BUFSIZ プログラム BUFSIZ ごと 小さい単位 バッファ : 一時的にデータを保存しておく場所のことバッファリング : バッファを経由してデータをやり取りすること バイト単位の遅いシステムコールより 素早く読み書きができる
FILE 型の正体 FILE 型 : ストリームを表現するために使用される型 FILE 型の中にはファイルディスクリプタと stdio バッファ情報が入っている ファイルディスクリプタ 正式名称 stdioでの変数名 意味 0 STDIN_FILENO stdin 標準入力 1 STDOUT_FILENO stdout 標準出力 2 STDERR_FILENO stderr 標準エラー出力
fopen fopen : stdio においてシステムコール open() に相当する API #include <stdio.h> FILE *fopen ( const char *path, const char *mode ) ; ファイルをパス指定するとファイルに繋がるストリームを作り FILE 型へのポインタを返す 第二引数の mode はストリームの性質を示す 値 mode 意味 r O_RDONLY 読み込み専用 w O_WRONLY_CREATEIO_TRUNC 書き込み専用 a O_WRONLY_CREATEIO_APPEND 追加書き込み専用 r+ O_RDWR 読み書き両用 w+ O_RDWRIO_CREATEIO_TRUNC 読み書き両用 a+ O_RDWRIO_CREATEIO_APPEND 読み書き両用
fclose fclose : stdio においてシステムコール close() に相当する API #include <stdio.h> int fclose ( FILE *stream ) ; ストリームを閉じる fopen で開いたファイルは必ず fclose で閉じなくてはならない
fgetc() と fputc() バイト単位の入出力 API #include <stdio.h> Int fgetc (FILE *stream ); Int fputc ( int c, FILE *stream ); fgetc( ) : 引数の stream から 1 バイト読み込んで バイト数を返すストリームが終了すると EOF を返す fputc( ) : 引数の stream にバイト c を書き込み 得たバイトをそのまま返す
getchar() と putchar 入力元と出力先が固定されているバイト単位の入出力 API #include <stdio.h> Int getchar (void); Int putchar ( int c ); getchar( ) : fgetc( stdin) と同じ意味つまり 標準入力から 1 バイト読み込んで バイト数を返す putchar( ) : fputc(c, stdout) と同じ意味つまり バイト c を標準出力に書き出し 得たバイトをそのまま返す
stdio 版 cat 1 次のプログラムを cat2.c という名前で作成し 保存してください #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int i; for (i = 1; i < argc; i++) { FILE *f; int c; f = fopen(argv[i], "r"); if (!f) { perror(argv[i]); exit(1); }
stdio 版 cat 2 次のプログラムを cat2.c という名前で作成し 保存してください } while ((c = fgetc(f))!= EOF) { if (putchar(c) < 0) exit(1); } fclose(f); } exit(0); ビルドして 実行してみましょう >gcc -o cat2 cat2.c >ls >./ cat2 hello.c args.c bell.c > out2.txt
文字列処理 1 ルール 文字列 ( バイト列 ) の末尾に \n を付けることで1 行を意味する例文 文字列 Hello World! \n もじもじもじもじもじ \n Gooooooooooooogle \n 行の列 Hello World! \n もじもじもじもじもじ \n Gooooooooooooogle \n
文字列処理 2 fgets() 文字列の行単位の入力 API #include <stdio.h> char *fgets ( char *buf, int size, FILE *stream ); fgets : ストリーム stream から 1 行読み込んで 第 1 引数の buf に格納するこの際 最大 size-1 バイトまでしか読み込まない正常に読み込めば buf を返す読み込みに失敗した場合には NULL を返す
文字列処理 3 fputs() 文字列の行単位の出力 API #include <stdio.h> int fputs ( const char *buf, FILE *stream ); fputs : 文字列 buf を stream に書き込む正常に読み込めば 0 を返す
文字列処理 4 printf() と fprintf() 文字列の出力 API #include <stdio.h> int printf ( const char *fmt,....... ); int fprintf ( FILE *stream, const char *fmt,....... ); printf : 引数 fmt で指定した体裁に従って 後続の文字列を標準出力する fprintf : 引数 fmt で指定した体裁に従って 後続の文字列を stream に出力する printf は便利なため多用されるが バッファオーバーフローの原因になる バッファオーバーフロー : バッファをはみ出してメモリを使ってしまうことデータが破壊されるなどの問題を生じる
固定長入出力 Stdio 経由で行う固定長バイト列の入出力 #include <stdio.h> size_t fread ( void *buf, size_t size, size_t nmemb, FILE *stream ) ; size_t fwrite (const void *buf, size_t size, size_t nmemb, FILE *stream ); fread : stream から size nmemb バイトを読み込み buf に格納する読み込みに成功すれば nmemb を返す失敗すると nmemb より小さい値を返す fwrite : size nmemb 分のバイト列を buf から stream に書き込む読み込みに成功すれば nmemb を返す失敗すると nmemb より小さい値を返す read() や write() はシステムコールだが fread() と fwrite() はライブラリ関数システムコールは UNIX でしか使えないが ライブラリ関数は C 言語で使える
ファイルオフセット操作 ファイルオフセット操作の API #include <stdio.h> int fseek ( FILE *stream, long offset, int whence ) ; long ftell (FILE *stream ); void rewind (FILE *stream ); fseek : stream のファイルオフセットを whence と offset で示される位置に移動する ftell : stream のファイルオフセットの位置を返す rewind : stream のファイルオフセットの位置を先頭に戻す lseek() はシステムコールだが fseek() はライブラリ関数システムコールは UNIX でしか使えないが ライブラリ関数は C 言語で使える
ラッパー FILE 型 : ストリームを表現するために使用される型 ラッパー : API で覆って機能を追加すること FILE はファイルディスクリプタのラッパーである stdio ストリームカーネル バッファ 追加機能 ストリーム A 0 プロセス FILE ストリーム B 1 1 ストリーム C 2 ファイルディスクリプタ
ファイルディスクリプタと FILE 操作 ファイルディスクリプタと FILE 操作の API #include <stdio.h> int fileno ( FILE *stream ) ; FILE *fdopen ( int fd, const char *mode ); fileno : stream がラップしているファイルディスクリプタの値を返す fdopen : ファイルディスクリプタ fd をラップする FILE 型の値を新しく作成し ポインタを返す 第二引数の mode はストリームの性質を示す 値 mode 意味 r O_RDONLY 読み込み専用 w O_WRONLY_CREATEIO_TRUNC 書き込み専用 a O_WRONLY_CREATEIO_APPEND 追加書き込み専用 r+ O_RDWR 読み書き両用 w+ O_RDWRIO_CREATEIO_TRUNC 読み書き両用 a+ O_RDWRIO_CREATEIO_APPEND 読み書き両用
stdio 動作演習 1 strace コマンドを使って 以前作成した hello の動作を見てみましょう >strace./hello > /dev/null execve("./hello", ["./hello"], [/* 56 vars */]) = 0 uname({sys="linux", node="cl0028",...}) = 0 brk(0) = 0x804955c old_mmap(null, 4096, PROT_READ PROT_WRITE, MAP_PRIVATE MAP_ANONYMOUS, -1, 0) = 0x40015000 open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/local/lib/i686/mmx/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory) stat64("/usr/local/lib/i686/mmx", 0xbfffee64) = -1 ENOENT (No such file or directory) open("/usr/local/lib/i686/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory).. 簡単なプログラムですが 色々な動作を行っていることが分かります
stdio 動作演習 2 以前作った cat を使って システムコール read, write, open, close の動作を見てみましょう >strace -e trace=open,read,write,close./cat hello.c > /dev/null.. close(3) = 0 open("/lib/i686/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\340\\\1"..., 512) = 512close(3) = 0 open("hello.c", O_RDONLY) = 3 read(3, "#include <stdio.h>\n\nint\nmain(int"..., 2048) = 102 write(1, "#include <stdio.h>\n\nint\nmain(int"..., 102) = 102 read(3, "", 2048) = 0 close(3) = 0 2048 バイト単位の read が 1 回 102 バイト単位の write が 1 回行われています 102 2=204 バイトの転送が行われています
stdio 動作演習 3 以前作った cat2 を使って システムコール read, write, open, close の動作を見てみましょう >strace -e trace=open,read,write,close./cat2 hello.c > /dev/null. open("/etc/ld.so.cache", O_RDONLY) = 3 close(3) = 0 open("/lib/i686/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\340\\\1"..., 512) = 512close(3) = 0 open("hello.c", O_RDONLY) = 3 read(3, "#include <stdio.h>\n\nint\nmain(int"..., 1024) = 102 read(3, "", 1024) = 0 close(3) = 0 write(1, "#include <stdio.h>\n\nint\nmain(int"..., 102) = 102 1024 バイト単位の read が 1 回 102 バイト単位の write が 1 回行われています 102 2=204 バイトの転送が行われていますメモリが 1024 バイト節約されているということですね バッファリング効果
第一回個別試験告知 日時 : 5 月 14 日 ( 木 ) 8:30~10:00 場所 : S14 生情報棟 5 階 AV 実習室 S10,S11,S12,S13 生大学院棟 4 階計算機実験室 当日スケジュール 8:40 答案配布 9:50 答案回収 ( 出席確認のため ) 10:00 までにメールにてプログラムを送付 他人との相談は厳禁です
本日の課題講義で作成した cat2.c を改良し コマンドライン引数でファイル名が渡されなかったら 標準入力を読み込むプログラムを作成しなさい cat2.c の場合 次のように実行すると何も起こらない >./a.out > 課題の場合 次のように実行すると標準入力を受け付ける >./a.out 5 標準入力 5 3 標準入力 3 2 標準入力 2 このようなプログラムを作成してください