C言語の標準入出力とは何かということをなるべく行間を作らずに解説しようという記事です。
ひとことで言えば、標準入出力とは、デバイスやプロセスの入出力のうち、アプリケーションで標準的に利用される入出力のことです。ターミナルでは、ユーザーのキーボード入力を標準入力としてアプリケーションに入力し、アプリケーションからの標準出力は画面に表示するようになっています。Unix/Linuxではデバイスやプロセスの入出力を、ファイルへの書き込み・読み込みとして扱います。従って、標準入出力もファイルの書き込み・読み込みの一種と捉えることができます。
例
次のようなプログラムを考えます。
#include <stdio.h>
int main(){
char data[16];
scanf("%s", data);
printf("%s\n", data);
return 0;
}
実行結果
$ ./a.out
Hello (自分で入力する)
Hello
このプログラムを実行するとハングし、文字列を入力しエンターキーを押すと先程入力した文字列をそのまま返します。このとき、入力した文字列Hello
は標準入力であり、出力された文字列Hello
は標準出力です。
実はこの標準入力・標準出力はファイルへの書き込み・読み込みとして実装することができます(しかもそちらの方が本質的です)。どういうことでしょうか?このことを理解するには次の節で説明するファイルの書き込み・読み込みの知識が必要です。
ファイルの書き込み・読み込みとファイルディスクリプタ
ファイルの書き込み・読み込みをするためには、ファイルを開く必要があります。オペレーティングシステムは開かれたファイルの状態をオープンファイル記述と呼ばれる構造体に保存します。オープンファイル記述は、現在ファイル内部のどの場所を読んでるか(あるいは書き込んでいるか)を示すオフセット値、ファイルが読み・書き・読み書き両方のうちどの利用目的でアクセスされているかを示すアクセスモードなどを含んでいます。
オープンファイル記述は現在オペレーティングシステムが開いているファイルの分だけあるわけですが、このオープンファイル記述を指し示す整数のことをファイルディスクリプタと言います。ファイルディスクリプタとオープンファイル記述の対応関係はファイルディスクリプタテーブルに保存されます。ファイルディスクリプタテーブルはプロセスごとに独立に存在しています。
例
次のプログラムを考えます。あらかじめHello
と入力したテキストファイルa.txt
を用意しておきます。
#include <stdio.h>
#include <fcntl.h>
int main(){
// "a.txt"を開く。第2引数はアクセスモードで、今回は読み込みO_RDONLYを指定
// フラグO_RDONLYはfcntl.hで定義されている
// fdはファイルディスクリプタ
int fd = open("a.txt", O_RDONLY);
printf("%d\n", fd);
return 0;
}
実行結果
$ ./a.out
3
この場合、ファイルディスクリプタは3
であることがわかりました 。
このファイルディスクリプタを用いてファイルの読み込み・書き込みができます。
- 読み込み
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
int fd = open("a.txt", O_RDONLY);
// ファイルディスクリプタを表示
printf("%d\n", fd);
// ファイルを読み込んだデータを保存する為の配列を定義
char buf[32];
// 現在のオフセット(ファイルの先頭)から32バイト分だけファイルを読み込んで、bufに書き込む
// char1文字は1バイトなので32文字読取ることになる
read(fd, buf, 32);
printf("%s\n", buf);
return 0;
}
実行結果
$ ./a.out
3
Hello
- 書き込み
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
int fd = open("a.txt", O_WRONLY);
// ファイルディスクリプタを表示
printf("%d\n", fd);
//書き込み用のデータの用意。多めに32バイト分確保。
char buf[32] = "Hello, World!";
//現在のオフセット(ファイルの先頭)から32バイト分だけファイルに書き込む
write(fd, buf, 32);
return 0;
}
実行結果
$ ./a.out
3
$ cat a.txt
Hello, World!
標準入力・標準出力・標準エラー出力とは
いよいよ本題です。標準入力・標準出力・標準エラー出力とは、それぞれのファイルディスクリプタが0
, 1
, 2
の読み込み・書き込みのことです。 というこは、冒頭の入力した文字列をそのまま返すプログラムもread()
, write()
関数で書き換えることができるはずです。やってみましょう。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
char buf[32];
// ファイルディスクリプタを0に指定することで標準入力を読み込む
read(0, buf, 32);
// ファイルディスクリプタを1に指定することで標準出力を書き込む
write(1, buf, 32);
return 0;
}
実行結果
$ ./a.out
Hello (自分で入力する)
Hello
確かにファイルディスクリプタを0
に指定することで標準入力を読み込み、ファイルディスクリプタを1
に指定することで標準出力を書き込むことがわかりました。
最後に
間違っている点があればお知らせください。