前へ << DNS クライアントを作ってみよう (3) | C 言語で HTTP クライアントを作ってみよう (1) >> 次へ |
C言語でファイルを扱うときは、一般的に fopen・fread・fwrite・fclose などの ライブラリ関数を使います。これらは stdio.h で宣言されており、 標準入出力ライブラリ (標準入出力関数) と呼ばれます。 ファイルをオープンするときは
FILE *fp; fp = fopen("file.dat", "r");として FILE 構造体を得ます。それに対して fread・fwrite を実行します。
しかし、これら fopen・fread などのライブラリ関数も、 内部では open・read・write・close などの、より低レベルな システムコールを呼び出しているのです。
標準入出力ライブラリではファイルを管理するのに FILE 構造体を使用していましたが、 システムコールではファイルディスクリプタと呼ばれる整数値を使います。 プロセスが実行されると同時に
では、標準入出力ライブラリを一切使わないサンプルプログラムを紹介します。
% cc -o file-open file-open.cでコンパイル。
% ./file-open filenameとすることで、ファイル filename の内容を標準出力へ出力します。
1: /* $Id: file-open.c,v 1.4 2004/07/18 11:37:55 68user Exp $ */ 2: 3: #include <fcntl.h> 4: #include <sys/types.h> 5: #include <sys/uio.h> 6: #include <unistd.h> 7: #include <stdio.h> 8: #include <string.h> 9: #include <errno.h> 10: 11: int main(int argc, char *argv[]){ 12: int fd; /* ファイルディスクリプタ */ 13: 14: /* 引数が指定されていなかったらエラー */ 15: if ( argc != 2 ){ 16: char err_message[] = "ファイル名を指定して下さい。\n"; 17: write(2, err_message, strlen(err_message)); 18: return 1; 19: } 20: 21: /* ファイルをオープン。オープンできなかったらエラー */ 22: fd = open(argv[1], O_RDONLY); 23: if ( fd < 0 ){ 24: char err_message[] = "ファイルをオープンできません。"; 25: write(2, err_message, strlen(err_message)); 26: write(2, strerror(errno), strlen(strerror(errno))); 27: write(2, "\n", 1); 28: return 1; 29: } else { 30: char message[256]; 31: sprintf(message, 32: "ファイル %s をオープンしました。ファイルディスクリプタは %d です。\n", 33: argv[1], fd); 34: write(1, message, strlen(message)); 35: } 36: 37: /* 256 バイト単位でファイルから読み込み、標準出力に書き出す */ 38: while (1){ 39: char buf[256]; 40: int read_size; 41: 42: read_size = read(fd, buf, sizeof(buf)); 43: 44: if ( read_size > 0 ){ 45: write(1, buf, read_size); 46: } else if ( read_size == 0 ){ 47: break; 48: } else { 49: char err_message[] = "read(2) でエラーが発生しました。"; 50: write(2, err_message, strlen(err_message)); 51: write(2, strerror(errno), strlen(strerror(errno))); 52: write(2, "\n", 1); 53: return 1; 54: } 55: } 56: close(fd); 57: return 0; 58: }
15: if ( argc != 2 ){ 16: char err_message[] = "ファイル名を指定して下さい。\n"; 17: write(2, err_message, strlen(err_message)); 18: return 1; 19: }ファイルディスクリプタ 2番は標準エラー出力を指しますので、 この write(2) は標準エラー出力宛に出力します。 printf(3) などの標準入出力ライブラリは、 文字列の中で文字列の終端を意味する '\0' を見付けるとそこで処理をやめてくれるので、 いちいち文字数を指定する必要はありません。 しかし write(2) は何文字出力するのかをプログラマが管理しなくてはいけません。 指定した文字数を出力し終わるまでは、途中で '\0' があっても処理を続けます。
上記の部分を標準入出力ライブラリで書き直すと
if ( argc != 2 ){ fprintf(stderr, "ファイル名を指定して下さい。\n"); return 1; }に相当します。
22: fd = open(argv[1], O_RDONLY); 23: if ( fd < 0 ){ 24: char err_message[] = "ファイルをオープンできません。"; 25: write(2, err_message, strlen(err_message)); 26: write(2, strerror(errno), strlen(strerror(errno))); 27: write(2, "\n", 1); 28: return 1;open(2) の第一引数はファイル名、第二引数はフラグです。 フラグのうち基本的なものを以下に示します。
fd = open(argv[1], O_WRONLY | O_APPEND | O_CREAT);などと論理 OR を取ることで複数のフラグを指定することもできます。
open(2) はオープンに成功するとファイルディスクリプタ (整数値) を返しますが、失敗すると -1 が返されます。このとき errno を参照することでエラーになった原因がわかります。 errno には
% file-open hoge.txt ファイルをオープンできません。No such file or directoryなどと出力されます。日本語ロケールを使用している場合は、
% file-open hoge.txt ファイルをオープンできません。ファイルまたはディレクトリがありません。などと日本語で表示される場合があるかもしれません。
なお、標準入出力ライブラリで書きなおすと
FILE *fp; fp = fopen(argv[1], "r"); if ( fp == NULL ){ fprintf(stderr, "ファイルをオープンできません。%s\n", strerror(errno)); return 1; }となります。
29: } else { 30: char message[256]; 31: sprintf(message, 32: "ファイル %s をオープンしました。ファイルディスクリプタは %d です。\n", 33: argv[1], fd); 34: write(1, message, strlen(message)); 35: }これは処理内容とは全く関係なく、ただのデバッグ情報です。 ここではファイルディスクリプタが何番かを表示しています。 使用中のディスクリプタは 0〜2 (それぞれ stdin、stdout、stderr) なので fd の値は 3 になる OS が多いと思われます。
38: while (1){ 39: char buf[256]; 40: int read_size; 41: 42: read_size = read(fd, buf, sizeof(buf)); 43: 44: if ( read_size > 0 ){ 45: write(1, buf, read_size); 46: } else if ( read_size == 0 ){ 47: break; 48: } else { 49: char err_message[] = "read(2) でエラーが発生しました。"; 50: write(2, err_message, strlen(err_message)); 51: write(2, strerror(errno), strlen(strerror(errno))); 52: write(2, "\n", 1); 53: return 1; 54: } 55: }read(2) で、ファイルディスクリプタ fd から sizeof(buf) の長さだけ読み込み、buf に格納します。 つまり、オープンしたファイルから 256 バイトを読み込み、buf に格納するわけです。 read(2) は実際に読み込んだバイト数を返します。
そして、buf から read_size バイトだけ ファイルディスクリプタ1番 (stdout) に出力します。 もし read_size が 0 なら、 読み込むべきデータがなかった (EOF) ということなので終了します。
もし指定したファイルの長さが 600 バイトなら、
また、read(2) でエラーが発生した場合は -1 を返しますので、 その場合は errno に相当する文字列を strerror(3) で取得し、 表示します。
56: close(fd); 57: return 0; 58: }最後に close(2) でファイルをクローズして終了です。 close のエラー処理は省略しています。
前へ << DNS クライアントを作ってみよう (3) | C 言語で HTTP クライアントを作ってみよう (1) >> 次へ |
ご意見・ご指摘は Twitter: @68user までお願いします。