ファイル記述子、ファイル型、ファイルポインタ、stdin、stdout、stderrについての考察


C言語の文法的な意味においては
ファイル記述子はファイルポインタが参照する構造体(ファイルシステムのiノードリストに含まれる利用者ファイル記述子表の構造体)の持つ要素のうちの一つ(int _fileno)です。

FILE型の定義を調べてみます。

$ less -N /usr/include/stdio.h


     48 /* The opaque type of streams.  This is the definition used elsewhere.  */
     49 typedef struct _IO_FILE FILE;


なるほど。48-49行目のところでFILE型の実体は、_IO_FILEという構造体であることがわかります。

_IO_FILEは、stdio.hがインクルードしているヘッダファイルの/usr/include/libio.hに記述されています。それを見てみます。

$ less -N /usr/include/libio.h


    271 struct _IO_FILE {
    272   int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
    273 #define _IO_file_flags _flags
    274
    275   /* The following pointers correspond to the C++ streambuf protocol. */
    276   /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
    277   char* _IO_read_ptr;   /* Current read pointer */
    278   char* _IO_read_end;   /* End of get area. */
    279   char* _IO_read_base;  /* Start of putback+get area. */
    280   char* _IO_write_base; /* Start of put area. */
    281   char* _IO_write_ptr;  /* Current put pointer. */
    282   char* _IO_write_end;  /* End of put area. */
    283   char* _IO_buf_base;   /* Start of reserve area. */
    284   char* _IO_buf_end;    /* End of reserve area. */
    285   /* The following fields are used to support backing up and undo. */
    286   char *_IO_save_base; /* Pointer to start of non-current get area. */
    287   char *_IO_backup_base;  /* Pointer to first valid character of backup area */
    288   char *_IO_save_end; /* Pointer to end of non-current get area. */
    289
    290   struct _IO_marker *_markers;
    291
    292   struct _IO_FILE *_chain;
    293
    294   int _fileno;
    295 #if 0
    296   int _blksize;
    297 #else
    298   int _flags2;
    299 #endif
    300   _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */
    301 
    302 #define __HAVE_COLUMN /* temporary */
    303   /* 1+column number of pbase(); 0 is unknown. */
    304   unsigned short _cur_column;
    305   signed char _vtable_offset;
    306   char _shortbuf[1];
    307 
    308   /*  char* _save_gptr;  char* _save_egptr; */
    309 
    310   _IO_lock_t *_lock;
    311 #ifdef _IO_USE_OLD_IO_FILE
    312 };


なんだか壮大なスケールの構造体の定義でしたが、目的のint_filenoを見つけ出すことができました。



ここで、あらかじめ定義されているファイルポインタstdin, stdout, stderrの定義内容を見てみます。


$ less -N /usr/include/stdio.h

    164 /* Standard streams.  */
    165 extern struct _IO_FILE *stdin;          /* Standard input stream.  */
    166 extern struct _IO_FILE *stdout;         /* Standard output stream.  */
    167 extern struct _IO_FILE *stderr;         /* Standard error output stream.  */

なるほど。stdin, stdout, stderrは_IO_FILE型の構造体を参照するポインタとして定義されているのですね。

以上のことより、下のようなプログラムを作ることができます。


// test.c
#include<stdio.h> /* printf, stdin, stdout, stderr */

int main(void)
{
//ファイルポインタの宣言
FILE *fp;
fp = fopen("./inputfile", "r");

/*各々のファイルポインタが参照するFILE型の構造体の_filenoを
 表示させる。
 _filenoとはファイルシステムのiノードリスト内に存在する
 iノードリトにある、「利用者ファイル記述表」というFILE型の構造体
 が連結されたリストの順番を示す番号である。  */

/*stdin, stout, stderrはstdio.hで定義されている
 ファイルポインタである。*/
printf("stdin  : %d\n", stdin->_fileno);
printf("stdout : %d\n", stdout->_fileno);
printf("stderr : %d\n", stderr->_fileno);
printf("fp     : %d\n", fp->_fileno);

//ファイルのクローズ
close(fp);


return 0;
}

#コンパイル
$ gcc -o test test.c

#実行
$ ./test 
stdin : 0
stout : 1
stderr: 2
Segmentation fault

#inputfileという空ファイルを作成
$ touch inputfile

#3番のファイル記述子がちゃんと割り当てられていることが確認できる。
$ ./test 
stdin : 0
stout : 1
stderr: 2
fp    : 3