原書で学ぶ64bitアセンブラ入門(10)
14章、入出力ストリームについてです。
Using the C stream I/O functions
システムコールの章では、入出力に関してopen
read
write
close
というシステムコールのラッパー関数について見てきました。この章ではバッファ付きI/Oを使う関数について見ていきます。
バッファ付きI/Oを使った方が読み込みが効率的になり、その仕組みは次のようになっています。例として1byteの読み込みを実行すると仮定します。
- 1byteをバッファから読み込もうとする
- バッファに対象のデータが無い
- バッファを満たすだけのデータ(一般的に8192byte)をバッファに読み込む
- バッファに読み込まれたデータから目的の1byteを取得する
この際、バッファからの読み込みは高速な為、ある程度のbyte数を読み込む必要がある場合は最初以外バッファとのやり取りになるので高速に行えます。つまり、8192byte以下の容量ならば実際に呼んでいるシステムコールは1回だけで満足させることが出来ます。
Opening a file
ストリームI/O関数を使ってファイルを開くには、fopen
を使います。fopen
のプロトタイプは
FILE *fopen ( char *pathname, char *mode );
となります。まぁ、よく見るfopen
そのままですね。第1引数がファイル名で第2引数がモードです。モードについても今更ですが、折角本にも書いてあるので一応記しておきます。
mode | 内容 |
---|---|
r | 読み込みのみ |
r+ | 読み書き |
w | 書き込みのみ。ファイルは初期化されるか新規作成する |
w+ | 読み書き。ファイルは初期化されるか新規作成する |
a | 書き込みのみ。ファイルに追記するか新規作成する |
a+ | 読み書き。ファイルに追記するか新規作成する |
fopen
の返値はFILEオブジェクトのポインタです。システムコールのopen
では返値がファイルディスクリプタの番号だったので、ここら辺が異なりますね。まぁ、第2、第3引数も違いますが・・・。
FILEオブジェクトについての詳細には本書では触れていません。そこまで知る必要が無いということで、大抵の場合、FILEオブジェクトはファイルといくつかのファイルについての"状態監視"データ要素へのバッファのポインタを内包した構造体であるとだけ触れています。
返値はポインタということで、アセンブラではquad-wordサイズの領域を確保しておけばそこに保存し、後で使うことが出来ます。ということで、実際に使ってみるとこの様になります。
fscanf and fprintf
fscanf
fprintf
は第1引数にFILEオブジェクトへのポインタを指定出来るようになっています。scanf
printf
は第1引数をそれぞれstdin
stdout
に固定してfscanf
fprntf
を呼び出すといった程度の違いです。
fgetc and fputc
fgetc
fputc
のプロトタイプは
int fgetc ( FILE *fp );
int fputc ( int c, FILE *fp );
fgetc
の返値は基本的に読み込まれた文字。fputc
の返値は基本的に書き込んだものと同じ文字です。
さて、fgetcで1文字を取得した後、余分に取りすぎた場合を考慮し、その1文字をストリームへ戻すための関数ungetc
があります。プロトタイプは
int ungetc ( int c, FILE *fp );
ungetc
で戻せるのは1文字だけで、繰り返しungetc
を実行したからといって際限なくストリームへ戻せるというわけではありません。
この館数の使いどころとして真っ先に思い浮かぶのが構文解析を行うケースで、例えば12345,23456
という,
区切りの2数値について考えてみると、12345
までを1文字ずつ取得して数値として解析していき、,
をfgetc
してストリームから得た段階で数値が終わっていた事が判明します。この1字先読みして取得してある,
を一旦ストリームへ返す事により、数値である範囲を解析した後、それまで行ってきた解析のループ上で,
を解析して必要な処理を行う等の対応が可能になります。これが無いと処理が一般化しづらくて大変ですね。
fgets and fputs
fgets
fputs
。この辺りもよく見かけるモノなので、特別な説明はここでする必要も無いでしょう。一応、プロトタイプは
char *fgets ( char *s, int size, FILE *fp );
int fputs ( char *s, FILE *fp );
本書ではそれぞれの実行時に終端文字の扱いがどうなるかがあれこれ書いてあるので、それらをまとめると
fgets
- 改行を読み込んだなら、バッファも改行が保存される
- 常に読み込んだデータの最後に0x00を置く
fputs
- 改行や終端に0x00を加えるといった操作は使用者の責務
fread and fwrite
fread
fwrite
関数はデータの配列を読み書きするように設計されています。プロトタイプは
int fread ( void *p, int size, int nelts, FILE *fp );
int fwrite (void *p, int size, int nelts, FILE *fp );
第1引数は配列。型は問わず。第2引数は配列要素のサイズで、第3引数は配列の要素数。nelts
はおそらくnumber of elementsの略?最後は言わずもがな、FILEオブジェクトへのポインタ。
customers配列の100要素をファイルへ書き込むにはこんな感じ
mov rdi, [customers]
mov esi, Customer_size
mov edx, 100
mov rcx, [fp]
call fwrite
fseek and ftell
本書では関数の引数説明程度のことしか書かれてないので、適当にman
でも見てください。
fclose
ストリームを閉じる関数です。ストリームはまだデバイス上へ書き込まれていないデータをバッファに持っているかもしれないので、ちゃんとfcloseを呼び出して書き込ませましょう。場合によってはまだ書き込まれていなかったデータが消失する事になります。