わらばんし仄聞記

南の国で引きこもってるWeb屋さん

原書で学ぶ64bitアセンブラ入門(10)

14章、入出力ストリームについてです。

Using the C stream I/O functions

システムコールの章では、入出力に関してopen read write closeというシステムコールのラッパー関数について見てきました。この章ではバッファ付きI/Oを使う関数について見ていきます。

バッファ付きI/Oを使った方が読み込みが効率的になり、その仕組みは次のようになっています。例として1byteの読み込みを実行すると仮定します。

  1. 1byteをバッファから読み込もうとする
  2. バッファに対象のデータが無い
  3. バッファを満たすだけのデータ(一般的に8192byte)をバッファに読み込む
  4. バッファに読み込まれたデータから目的の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を呼び出して書き込ませましょう。場合によってはまだ書き込まれていなかったデータが消失する事になります。