パソコン活用研究シリコンバレー(C、C++、の活用研究)

低水準入出力関数

=======================================================================================
ファイル入出力関数でファイル入出力関数として、まずfopen, fclose, putc, fputc, getc, fgetc などの説明
をしましたが、これらはOS(処理系)に依存しない高水準入出力関数と呼ばれるものでした。
他方で、OS(処理系)に依存する低水準入出力関数が存在します。高水準入出力関数で記述できるもの
は、高水準入出力関数で記述しておくべきだと思いますので、あえて低水準入出力関数を使うケースは
少ないと思います。が、低水準入出力関数について知っておいて損はない(?)ので、一応まとめてみました。
=======================================================================================

1 ファイルのオープン/クローズ

ファイルのオープン/クローズには、open, closeを使います。
(高水準ではfopen, fclose でしたね)

まず、低水準入出力関数の特徴について簡単に説明します。よくわからなければ、読み飛ばして
下さい。さしあたり、使い方さえわかれば問題ないので、わからなくて困ることはないでしょう。
高水準関数のfopen, の場合には関数の戻り値はファイル構造体へのポインタでしたが、低水準
関数のopen, の戻り値はファイルハンドルです。ファイルハンドルはOSがファイルを管理するため
の識別番号のようなものです。ファイルハンドルにはあらかじめ値が設定されているものがあり、
MS-DOSの場合は標準入力(デフォルトではキーボード)が0、標準出力(デフォルトではディスプ
レイ)が1、標準エラー出力が2になっています。従ってユーザーがopenしたファイルには3以降の
ファイルハンドルが割り振られます。(場合によっては、標準補助入出力に3、標準プリンタ出力に4
があらかじめ設定される仕様のこともあります。その場合ユーザーのopenしたファイルには5以降の
ファイルハンドルが割り振られます。)

このように、低水準入出力関数はOSが使用するファイルハンドルをファイル操作のために直接使用
するため、OS依存の関数となっています。このため他の処理系とは互換性がなく、移植性が低い
関数です。
MS-DOS用のコンパイラの場合、低水準入出力関数の機能は、INT21のシステムコールとほぼ同機能
です。(Turbo CにはMS-DOSのシステムコール0x3Dを直接呼び出す_open という関数も用意され
ています。)


最初にファイルを開くための関数openについて説明します。ファイルの読み書きを可能にするため
には、openでファイルを開かなければなりません。openすると、ファイルの識別番号であるファイルハ
ンドルが戻り値として返ってきます。以下ファイルの読み込み、書き込み他の操作の時には、この
ファイルハンドルでどのファイルへの操作かを指定してやることになります。
以下にLSICの場合のopenの書式を記載します。コンパイラにより若干仕様の異なることがあります
ので、お使いのコンパイラの仕様書を確認して下さい。

open
【書式】 #include <fcntl.h>
     #include<sys/stat.h>
     #include<sys/types.h>
     #include<io.h>
      int open(filename, flag [, mode]);
      char *filename;
      int flag;
      int mode
返り値 成功の場合はファイルハンドル、失敗(エラー)の場合は-1が返る 

filename はオープンするファイル名(パスを含む)
flag, modeは以下のものを指定します。
 <フラグ>
以下のフラグの論理輪を記述します。

O_RDONLY 読み出しのみ可
O_WRONLY 書き込みのみ可
O_RDWR 読み書き共に可
O_BINARY バイナリモードでオープンします
O_APPEND このフラグがセットされていると、書き込みを実行する直前にファイル
       ポインタはファイルの最後にシークします
O_CREAT 新規ファイル作成をします。すでにファイルが存在する場合、何もしません。
    ファ
イルが存在しない場合には新たなファイルを作成します。
O_TRUNC 新規ファイルを作成します。ファイルがすでに存在する場合、既存ファイル
    長は0 に切りつめられます(すなわち空になる)。

O_EXCL  O_EXCL がO_CREATと共に指定されている場合、すでにファイルが存在し
       ているときにはopen()は失敗します

<モード>
modeはO_CREATを指定して新たなファイルを作成するときに必要となり、ファイルの
保護モードを指定します。<sys/stat.h>で定義されている定数を使
って次のように
指定します。

S_IWRITE 書き込み可
S_IREAD 読み出し可(リードオンリーファイルを作ります)
S_IREAD | S_IWRITE 読み書き可

ただし、MS-DOS ではライトオンリーのファイルは作成できませんので、 S_IWRITEと
S_IREAD | S_IWRITEとは同じ意味となり、いずれもリードライト可能なファイルになり
ます。


例えば、data.binというバイナリーファイルをバイナリーモードでオープンする時は、
int fd;
fd = open("data.bin", O_RDWR | O_BINARY);
のように記述します。

テキストモードでオープンすると、改行復帰の制御コード(&H0D, &H0A)、終端コード(&H1A)を制御コード
として処理します。

ちなみに高水準入出力関数fopenの戻り値は、ファイル構造体へのポインタですが、ファイル構造体
のメンバーのひとつとして、ファイルハンドル(=ファイルディスクリプタ)があります。すなわち、高水準
入出力関数も、ファイル構造体を通してファイルハンドルを参照しているわけです。
FILE構造体については、FILE構造体を参照して下さい。


次はファイルをクローズする関数closeです。
close()はファイルハンドルfdで示されるファイルをクローズし,そのハンドルを他の
ファイルに使用できるように解放します。

close
【書式】
#include <io.h>
int close(fd);
int fd;

返り値は、0 クローズ成功   -1 失敗 


最後にファイルを新規作成する関数creatについて説明します。ファイルの新規作成はopenで
フラグでO_CREATを指定しても可能です。すなわち、
open(path, O_CREAT | O_TRUNC | O_WRONLY, mode);
でcreatと同等の機能が実行されます。

creat
【書式】
#include<sys/types.h>
#include<sys/stat.h>
#include<dos.h>
int creat(filename, mode);
char *filename;
int mode;

返り値 成功の場合はファイルハンドル、失敗(エラー)の場合は-1が返る 

filename はオープンするファイル名(パスを含む)
modeは以下のものを指定します。
<モード>

S_IWRITE 書き込み可
S_IREAD 読み出し可(リードオンリーファイルを作ります)
S_IREAD | S_IWRITE 読み書き可



2 ファイルの入出力
オープンしたファイルに対し、入出力を行ったり、ファイルの現在位置を取得・変更する関数です。

関数 書式 返り値 機能
read int read(fd,buf,n);
int fd;
void *buf;
int n;
正常:読み込んだバイト数
ファイルエンド:0
エラー:-1
ファイルfdからbufにnバイト読み込みます。読み込みはファイルの現在位置から行われ、読み込み後、ファイル位置はnバイト進みます。
write int write(fd, buf,n);
int fd;
void *buf;
int n;
正常:書き出したバイト数
エラー:-1
バッファbufからnバイトをファイルfdに書き出します。
eof int eof(fd);
int fd;
ファイルエンドなら1、そうでないなら0 ファイルfdがファイルエンドになったかどうか判定します
lseek long lseek(fd,offset,mode);
int fd;
long offset;
int mode;
正常:移動後のファイル位置
エラー:-1
ファイルの現在位置をmodeで指定した位置からoffsetバイト移動します。
modeは以下の通り
SEEK_SET ファイルの先頭からの絶対位置SEEK_CUR 現在の位置からの相対位置
SEEK_END ファイルの最後からの相対位置
tell long tell(fd);
int fd;
正常:ファイルの現在位置
エラー:-1
ファイルfdの現在位置を、ファイルの先頭からのバイト数で返します。

これらの関数の実際の使い方は、以下の簡単なサンプルプログラムを参照して下さい。
lseek,tellを使ったサンプルプログラムは別の機会に掲載したいと思います。


3 プログラム例
簡単な例として、ファイルのコピーをするプログラムを作成してみます。
MS-DOSのcopyコマンドと同じ機能(ただし、ワイルドカードは使えないシンプルなもの)です。
>cp2 file1 file2
でfile1を複製してfile2を作ります。
ファイル入出力関数では、高水準入出力関数でも同様のコピープログラムを作成してみましたので、
比較してみて下さい。
ただし、こっちの方がちょっとだけ機能が豊富です。file2のファイル形式が8.3形式として正しいかどうか
をchecknameでチェックしています。またオプションで-eをつけるとfile1を削除します。

/* cp2.c  copy file2  ver 0.0.1  for LSI C*/

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>

int checkname(char *);

main(argc, argv)
int argc;
char **argv;
{ int n, fd, fd2;
  void *buff;

/* コマンドライン引数がないまたは不足の場合は使用方法を表示 */
if(argc < 3) {
 printf("Copy File2 Ver0.0.1\n");
 printf("****  Usage  ****\n");
 printf("cp2 SorceFile DestinationFile\n");
 printf("Option -r SourceFileを削除します\n");
 exit(0);}

if((fd = open(argv[1], O_RDWR | O_BINARY)) == -1) {
printf("Cannot open File : %s\n",argv[1]);
exit(1);}

n=checkname(argv[2]);
if (n ==-1) {printf("DestinationFile名の形式が正しくありません\n");exit(1);}

if ((fd2=creat(argv[2], S_IREAD | S_IWRITE))==-1){
  printf("DestinationFileを作成できませんでした"); exit(1);}

while(eof(fd)==0) {
int cc;
cc=read(fd,buff,1);
if(cc==-1){
printf(" Read Error \n");
break;}

cc=write(fd2,buff,1);
if(cc==-1){
printf(" Write Error \n");
break;}
}

/* -r オプションの処理 */
if ((argc == 4) && (strcmp(argv[3],"-r")==0 ) ){remove(argv[1]);}

close(fd);
close(fd2);
}

/* ファイル名が8.3形式として正しいかチェック */
int checkname(argv)
char *argv;
{int l,m,n;
l=strlen(argv);
argv=argv+l-1;
n=0;
while(*argv !='.'){
n++;
argv--;
if (n>3) {return(-1);}
}
m=0;
while(*argv != '\\'){
m++;
argv--;
if (m>8){return(-1);}
if (n+m>l) {return(0);}
}
if (n>8) {return(-1);}
else {return(0);}
}

シンプルなプログラムなので、説明する部分もほとんどありませんが、
>cp2 file1 file2
で、コピー元file1用のファイルハンドルとしてfd、コピー先file2用のファイルハンドルとしてfd2
を用意して使用しています。

fd = open(argv[1], O_RDWR | O_BINARY)
で、コピー元file1は、読み書きともに可、バイナリーモードでオープンしています。

コピー先file2は
fd2=creat(argv[2], S_IREAD | S_IWRITE)
で、こちらも読み書きともに可でファイルを新規作成しています。

-r オプションの処理については
if ((argc == 4) && (strcmp(argv[3],"-r")==0 ) ){remove(argv[1]);}
で、コマンドラインからの引数が3個で、かつ、3個めの引数が"-r"の場合はコピー元file1は削除(remove)
するという動作をさせています。
strcmpという文字処理関数については、文字列処理関数をご覧下さい。

ファイル名が8.3形式として正しいかチェックするためのcheckname関数は一見、何をやっているのかわかり
にくいかもしれません。
簡単に説明すると、まずファイル名の終わりの方から拡張子("."の後ろ)が3文字かどうかチェックします。
次にファイル名本体の部分が8文字以内かどうかチェックします。問題がある場合は、return(-1)でリターンし、
問題なければreturn(0)でリターンします。
ファイル名本体が8文字以内かどうかの判定では、ディレクトリ名を含む場合とディレクトリ名を含まないで
ファイル名のみの指定(例えば、data\abc.c のようではなく、abc.c のようにファイル名のみ)の場合があります。
ファイル名のみの場合は、
if (n+m>l) {return(0);}
でファイル名チェック完了と判定してリターンしています。
ちょっとわかりにくいかもしれませんが、ポインタ変数argvをデクリメントしていますので、今どこを差しているか
よく注意してみてください。

TopPage