このページでは、標準出力・標準入力・標準エラー出力について、主にC言語の観点から解説していきます。
これらはプログラミングを行なっていると必ずと言っていいほど目にする単語だと思います。
ただ、これらの用語の理解があやふやという方も多いのではないでしょうか?
このページでは、これらの用語の意味がしっかり理解できるよう、標準出力・標準入力・標準エラー出力について図や実例を用いながら解説していきたいと思います!
Contents
標準出力
まずは、3つの中で一番分かりやすい標準出力について解説していきます。
標準出力 “以外” の出力として「ファイル出力」を取り上げ、ファイル出力と比較しながら「標準出力」について解説していきたいと思います。
標準出力以外の出力
まずは、ファイル出力を行う際の処理の流れを確認してみましょう!
今回は簡単のため、文字列をファイルに出力する例で考えていきたいと思います。
この文字列のファイル出力は、例えば下記のような処理により実現することができます(エラーハンドリング処理は省略しています)。
#include <stdio.h>
int main(void) {
FILE *stream1 = fopen("output1.txt", "w");
FILE *stream2 = fopen("output2.txt", "w");
fprintf(stream1, "Hello World");
fprintf(stream2, "Good Bye");
fclose(stream1);
fclose(stream2);
}
コンパイルして実行すれば、output1.txt
というファイルに Hello World
が出力され、output2.txt
というファイルに Good Bye
が出力されます。
非常に簡単な例となりますが、上記のソースコードには、標準出力を理解する上で重要になる要素が詰まっています。
出力ストリームの確立
ということで、先程のソースコードで行っていることを確認していきましょう!
まず最初に下記で fopen
関数を実行しています。
FILE *stream1 = fopen("output1.txt", "w");
FILE *stream2 = fopen("output2.txt", "w");
fopen
はファイルを開く関数であり、この関数を実行することでプログラムとファイルの間に「ストリーム」が確立されます。
ストリームとは、プログラムと入出力先を結ぶ経路上でデータの流れを実現するものです。
ストリームについては下記ページで解説していますので、詳しく知りたい方は下記ページをご参照いただければと思います。
C言語のファイル入出力について解説また、ストリームはバッファを持っており、そのバッファにデータを一時的に溜めるようなこともできます(データを溜めないストリームもある)。
この辺りは下記で紹介している fflush
関数や setvbuf
・setbuf
関数の解説の中で説明していますので、こちらも詳しく知りたい方は是非読んでみてください。
ストリームは抽象的な言葉なのでイメージが付きにくいという方もおられると思います。その場合は、まずはストリームを「経路」と考えて解説を読み進めていただければと思います。
もちろんストリームは単なる経路というわけではないのですが、標準出力等について理解する上で重要なのは「プログラムと入出力先を結ぶ入出力経路」ですし、経路と考えるとイメージも付きやすくなると思います。
さて、fopen
関数の第2引数に "w"
などの書き込みモードを指定した場合は、このストリームはプログラムからファイルの向きにデータが流れる出力ストリームとなり、この出力ストリームの接続先は fopen
関数の第1引数で指定したパスのファイルとなります。
したがって、上記の fopen
関数の実行により、プログラムから見て接続先を "output1.txt"
とする出力ストリームと、接続先を "output2.txt"
とする出力ストリームの2つが確立されることになります。
さらに、fopen
関数の返却値は、その fopen
関数実行によって確立された出力ストリームを指定するための識別子となります(fopen
関数の返却値の型は FILE *
であり、FILE
構造体の持つファイルディスクリプタによって識別される)。
例えば、これは後述でも解説しますが、fprintf
関数で文字列の出力を行う際に、この fopen
関数の返却値を指定して出力先のストリームを選択するようなことができます。
上記のソースコードでは fopen
関数の返却値をそれぞれ stream1
と stream2
に格納しているため、stream1
や stream2
によって出力先のストリームを選択することができることになります。
出力ストリームの指定を行なう
また、出力を行う際には、どこに出力するのかを出力先として関数等に指定する必要があります。
この出力先の指定および実際の文字列の出力を行なっているのが、前述のソースコードにおける下記部分となります。
fprintf(stream1, "Hello World");
fprintf(stream2, "Good Bye");
この fprintf
関数実行時にポイントとなるのが第1引数です。
第1引数には fopen
関数の返却値を指定しており、これにより fprintf
関数に対して出力先の出力ストリームを指定することができます。
1回目の fprintf
関数では第1引数に stream1
を指定していますので、接続先が "output1.txt"
である出力ストリームに "Hello World"
が出力されることになります。
そのため、"Hello World"
という文字列はストリームの接続先の "output1.txt"
に出力されることになります。
また、2回目の fprintf
関数では第2引数に stream2
を指定していますので、接続先が "output2.txt"
である出力ストリームに "Good Bye"
が出力されることになります。
そのため、"Good Bye"
という文字列はストリームの接続先の "output2.txt"
に出力されることになります。
重要なのは、プログラムの出力先はファイルではなく、あくまでも「出力ストリーム」であるという点になります。出力ストリームの接続先がファイルなので文字列がファイルに出力されているだけあり、同じストリームであっても、接続先を変更すれば他のものに出力されるようになります。
また、出力が完了した後は、最初に紹介したソースコードのように fclose
を実行して出力ストリームを閉じる必要があります。ただ、標準出力の理解に関しては fclose
はそこまで重要ではないので、以降ではこのストリームを閉じる説明に関しては省略させていただきます。
スポンサーリンク
標準出力とは
次は、本題の「標準出力」について、実際にソースコードを参照しながら特徴を確認していきたいと思います。
まず最初に言っておくと、先程 fopen
で確立した stream1
と stream2
同様に、標準出力は出力ストリームの1つになります。
ただし、標準出力 “以外” の出力ストリームに比較して “標準出力は特別な存在” であり、他の出力ストリームに無い特徴を持っています。その辺りをソースコードを参照しながら確認していきたいと思います。
今回用意したソースコードは下記になります。標準出力に文字列 "Hello World"
を出力する例となります。
#include <stdio.h>
int main(void) {
fprintf(stdout, "Hello World");
}
先程のファイル出力を行うプログラムも簡単でしたが、こちらはさらに簡単ですね!
単純に fprintf
関数を実行しているだけです。
上記のように fprintf
関数を実行すれば、画面に "Hello World"
が出力されます。
出力ストリームはあらかじめ確立されている
さて、ここで 標準出力以外の出力 で解説した内容について思い出してみましょう。
標準出力以外の出力 においては、出力を行うために下記の2つを行いましたね!
- 出力ストリームの確立
- 出力ストリームの指定
それに対し、先程紹介したソースコードでは「出力ストリームの確立」は行なっていません。
「出力ストリームの確立」が必要なのは、あくまでも標準出力以外の出力時のみであり、標準出力の場合は不要です。
その理由は、標準出力はあらかじめ確立されている出力ストリームだからです(各プロセスに対してあらかじめ確立されている)。そのため、標準出力への出力を行う場合、出力ストリームを別途確立するような処理は不要となります。
また、fopen
関数で確立した出力ストリームの接続先は fopen
関数の第1引数で指定したパスのファイルでした。
それに対し、標準出力の接続先はデフォルト設定では画面(ターミナルなどの画面)となります。なので、標準出力への出力を行なうと出力ストリームの接続先である画面に出力されることになります。
stdout
を出力ストリームに指定する
また、fprintf
関数での出力を行う際には、第1引数で「使用する出力ストリーム」を指定する必要があります。
標準出力以外への出力を行う場合は、fopen
関数で確立したストリームを使用するために fopen
関数の返却値(stream1
や stream2
)を指定しましたね!
ただ、標準出力の場合は fopen
関数等で別途ストリームを確立するわけではありませんので、出力ストリームを指定するために fopen
関数の返却値を指定することはできません。
その代わり、標準出力の場合は stdout
という定数マクロ(グローバル変数の場合もあるかも)が用意されており、この stdout
を利用して使用するストリームに標準出力を指定することができます。この stdout
は stdio.h
をインクルードすることで使用することができます。
ですので、上記のソースコードのように fprintf(stdout, "Hello World")
を実行すれば、標準出力に対して Hello World
が出力されることになります。そして、標準出力の接続先は画面ですので、その Hello World
は画面に出力されることになります。
ただ、皆さんもご存知のように、出力を行う関数であっても出力ストリームの指定が不要な関数も多く存在します。
一番馴染み深いのは printf
だと思います。この printf
関数では、出力する文字列の情報は引数で指定できるものの、使用する出力ストリームは指定できません。
こういった出力ストリームの指定が不要な出力関数においては、関数内部で使用する出力ストリームに標準出力(or 後述で紹介する標準エラー出力)を指定して出力が行われます。例えば printf
内部では第1引数に stdout
を指定して fprintf
関数が実行されていたります。
重要なのは、printf
などの出力ストリームの指定が不要な出力関数においても、確立された出力ストリームを利用して出力を行なっているという点です。
つまり標準出力とは?
ここまで解説してきたように、標準出力とは、あらかじめ確立されている出力ストリームになります。
この標準出力はプロセス毎(実行中のプログラム毎)に確立されています(これは後述で紹介する標準入力・標準エラー出力も同様です)。
デフォルト設定では標準出力の接続先は画面に設定されています。なので、プログラムが標準出力に文字列を出力すると、その文字列が標準出力の接続先である画面に出力されることになります。
また、標準出力を出力ストリームとして指定するためには stdout
を利用します。
さらに、標準出力への出力を前提とした標準ライブラリ関数が用意されています(printf
や puts
など)。
標準入力
ここまで標準出力に絞って解説を行なってきましたが、標準入力に関しては標準出力とほぼ同様です。
ただ、標準入力の場合は出力ストリームではなく入力ストリームとなります。
標準入力とは
標準入力はあらかじめ確立されている入力ストリームです。
標準入力の接続先はデフォルト設定ではキーボード(ターミナルに繋がれているキーボード)となります。なので、標準入力からデータの取得を行なった場合、入力ストリームの接続先であるキーボードからの入力を取得することになります。
また、標準入力を入力ストリームとして指定するためには stdin
を利用します。
さらに、標準入力への入力を前提とした標準ライブラリ関数が用意されています(scanf
や gets
など)。
スポンサーリンク
標準エラー出力
あらかじめ確立されている出力ストリームとしては、実は2つのストリームが存在します。
1つは前述で解説した標準出力で、もう1つがここで解説する標準エラー出力となります。
標準エラー出力とは
標準エラー出力はあらかじめ確立されている出力ストリームです。
主にエラー発生時のメッセージの出力に利用します。
標準エラー出力の接続先はデフォルト設定では画面(ターミナルなどの画面)となります。なので、標準エラー出力への出力を行なうと、ストリームの接続先である画面に表示されることになります。
また、標準エラー出力を出力ストリームとして指定するためには stderr
を利用します。
さらに、標準エラー出力への出力を前提とした標準ライブラリ関数が用意されています(perror
など)。
標準出力と標準エラー出力の違い
前述の通り、標準エラー出力はエラー時のメッセージ等を出力する際に利用されます。
でも、標準出力でも標準エラー出力でも同じ画面に出力されるのですから、これらを使い分ける必要はあるのでしょうか?
結論としては、使い分けた方が良いです。その理由について、標準出力と標準エラー出力の違いを踏まえながら解説していきます。
そもそも別のストリームである
前述の通り、標準エラー出力と標準出力の接続先は両方とも画面です。
なので、どちらに出力したとしても結局画面に出力されるので、これらを使い分ける必要がないようにも思えます。
実際、下記のように2つの文字列を標準出力に出力した場合と、
#include <stdio.h>
int main(void) {
fprintf(stdout, "Hello World\n");
fprintf(stdout, "Good Bye\n");
}
下記のように2つの文字列を標準出力と標準エラー出力とで別々に出力した場合とでは、実行結果としては同じになるのではないかと思います。
#include <stdio.h>
int main(void) {
fprintf(stdout, "Hello World\n");
fprintf(stderr, "Good Bye\n");
}
ただし、標準エラー出力と標準出力の接続先は両方とも同じ画面ではあるものの、この2つはあくまでも「別々のストリーム」です。
このストリームが別のものであることに大きな意味があります。
なぜなら、後述で解説するように、プログラムの変更なしにストリームの接続先は変更することが可能だからです(ストリーム毎に接続先を変更することが可能)。
デフォルト設定では同じ画面に表示されるとしても、ストリームの接続先を変更することで「標準出力への出力」と「標準エラー出力への出力」を別のものに出力することができるようになります。
例えば、標準エラー出力の接続先をファイルに変更してやれば、通常時のメッセージのみを画面に出力し、エラー発生時のメッセージは画面に表示せずに、代わりにファイルに出力するようなことができます。
ですが、こういった通常時のメッセージとエラー発生時のメッセージの出力先の分離を行うことができるのは、あらかじめ標準出力と標準エラー出力とを使い分けて出力している場合のみとなります。
あくまでも変更できるのはストリームの接続先であり、プログラムから全てのメッセージを同じストリームに出力してしまっていれば、ストリームの接続先を変更しても全てのメッセージの出力先が変化するだけです。
これだと出力先の分離を行うことができません。
なので、標準出力と標準エラー出力に関しては、後から接続先を変更されることを考慮して適切に使い分けることをオススメします。
このあたりに関しては最後の章の リダイレクト・パイプ でも簡単に解説します。
バッファリングの仕方が違う
また、標準出力と標準エラー出力とでは、バッファリングの仕方が異なる場合があります。
標準出力 で簡単に触れましたが、ストリームではデータのバッファリング(データを一時的に溜め込む)を行うことが可能です。
ただし、標準エラー出力の場合は、デフォルト設定でデータのバッファリングを行わないように設定されていることが多いです。
また標準出力の場合は、デフォルト設定でデータをラインバッファリングでバッファリングするように設定されていることが多いです。ラインバッファリングとは、改行文字が出力されるまでデータを溜め込むバッファリング方法になります。
この辺りのバッファリングの仕方の設定は環境によって異なる可能性がありますので注意してください。ひとまず私の環境では上記のようにバッファリングの仕方が設定されています。
ですので、例えば下記のソースコードのプログラムと、
#include <stdio.h>
int main(void) {
fprintf(stdout, "Hello World!");
fprintf(stdout, "Good Bye!");
}
下記のソースコードのプログラムとでは、画面への出力結果が異なります。
#include <stdio.h>
int main(void) {
fprintf(stdout, "Hello World!");
fprintf(stderr, "Good Bye!");
}
具体的には、前者の場合は下記のように出力が行われるのに対し、
Hello World!Good Bye!
後者の場合は下記のように出力が行われます。後から出力した Good Bye!
の方が先に画面に出力されていますね。
Good Bye!Hello World!
標準エラー出力の場合はバッファリングが行われないので直ちに接続先の画面に出力されますが、標準出力の場合はバッファリングが行われて標準出力に改行文字が出力されるまで接続先に出力されません(プログラム終了時やバッファが満杯になった時にも出力される)。
なので、後から行なった標準エラー出力への出力の方が先に画面に出力されることになります。
ちなみに、後者のソースコードの場合は標準出力に改行文字が出力されることがないため、プログラム終了時に Hello World!
が画面へ出力されることになります。
一応このような違いもあるのですが、バッファリングの仕方の違いに基づいて標準出力と標準エラー出力とを使い分けることはまず無いと思います。標準出力と標準エラー出力は、出力するメッセージの意味合い(エラー時のメッセージか否か等)に基づいて使い分けする方が良いです。
ただ、上記の例のように使用するストリームによって画面に出力されるタイミングが異なることもあるので、標準出力と標準エラー出力とでバッファリングの仕方に違いがあることは頭の片隅にでも置いておくと良いと思います。
スポンサーリンク
リダイレクト・パイプ
最後に標準出力・標準入力・標準エラー出力と深い関係を持つリダイレクトやパイプについて簡単に説明しておきます。
リダイレクト・パイプとは
このリダイレクトやパイプは、標準出力・標準入力・標準エラー出力の接続先を変更する仕組みであり、これらを利用することでプログラム実行時に標準出力・標準入力・標準エラー出力の接続先をデフォルト設定のものから変更することができます。
例えばですが、カレントディレクトリにある main.exe
というプログラムを下記のように実行すると、main.exe
からの標準出力への出力は画面に出力されることになります。これは標準出力の接続先が画面に接続されているからです。
% ./main.exe
それに対し、下記のように main.exe
を実行した場合、>output.txt
によって標準出力の接続先がカレントディレクトリの output.txt
というファイルに変更されることになります。
% ./main.exe >output.txt
そのため、単に ./main.exe
を実行した時に画面に出力されていた文字列は画面に出力されなくなり、代わりに新たな接続先である output.txt
というファイルに出力されることになります。
このような、ストリームの接続先の変更を行うことをリダイレクトと言い、上記における >output.txt
は標準出力の接続先を output.txt
に変更するというリダイレクトの命令となります。
このリダイレクトは標準出力・標準入力・標準エラー出力に対してそれぞれ個別に行うことができます。
また、パイプでも同様に、ストリームの接続先の変更を行うことができます。パイプは主にプログラム(プロセス)の標準出力を別のプログラム(プロセス)の標準入力に接続するために利用されます。
リダイレクトやパイプについては下記ページでも紹介していますので、詳しく知りたい方はこちらをご参照いただければと思います。
コマンドのリダイレクトについて解説(標準入力や標準出力の接続先を変更) パイプ | について解説( | と | xargs の違いも理解できる!)リダイレクト・パイプのメリット
このリダイレクトやパイプを利用するメリットについても説明しておきます。
プログラムの変更なしに入出力先を変更できる
このリダイレクトやパイプを利用するメリットは、プログラム実行者がプログラムの変更を行うことなく標準出力・標準入力・標準エラー出力の接続先を変更できるという点にあります。
先程の main.exe
の例で考えると、リダイレクトを利用しなかった場合、main.exe
からの出力先が標準出力から output.txt
への出力ストリームとなるよう main.exe
自体を変更する必要があります。つまり、使用するストリーム自体の変更が必要になります。
この場合、例えばですが、下記のように新たにストリームを確立し、出力時にそのストリームを使用するよう直接ソースコードに手を入れて変更する必要があります。
#include <stdio.h>
int main(void) {
/* 標準出力への出力箇所を、output.txtに接続される
出力ストリームへの出力に変更 */
/*
fprintf(stdout, "Hello World!");
fprintf(stdout, "Good Bye!");
*/
FILE *stream = fopen("output.txt", "w");
fprintf(stream, "Hello World!");
fclose(stream);
}
ソースコードの変更を行うだけでも面倒ですし、そもそもソースコードが入手できない、ソースコードの変更の仕方が分からない等の理由から変更できないような場合もあり得ます。
リダイレクトやパイプを利用すれば、プログラムからの出力先を標準出力としたまま、標準出力の接続先を変更するだけで、プログラムからの出力な最終的な出力先が変更可能となります。
標準出力と標準エラー出力を別の接続先に出力できる
また、標準出力と標準エラー出力の接続先はデフォルト設定では両方とも画面になっていますが、リダイレクトを利用することで標準出力と標準エラー出力を別々の出力先に接続させることも可能です。
例えば、標準出力の接続先をファイルに変更してやれば、標準エラー出力への出力のみ画面に出力されるようになります。
標準出力への出力が大量であるような場合、標準エラー出力への出力が埋もれてしまってエラーに気づけないようなこともあり得ます。ですが、別々の接続先に対して出力されるようにしておけば、そのようなことも防ぐことができます。
ただし、このようなリダイレクトで恩恵が受けられるのは、あくまでもプログラムで標準出力と標準エラー出力を適切に使い分けている場合のみです。
例えば、何も考えずにエラー発生時のメッセージ等も全て標準出力から出力するようにしていた場合、ストリームの接続先を変更したとしても結局全て同じ接続先に出力が行われることになります。
この場合、通常時のメッセージとエラー発生時のメッセージを個別に出力するためにはプログラム自体を変更する必要があります。前述の通り、プログラミングの知識不足やソースコードが入手不可の場合などは、プログラムの変更ができない場合もあります。
そうなると、せっかく良いプログラムでも結局ユーザーにとって使いにくいプログラムになってしまいます。
なので、特に他の人に利用されるようなプログラムを開発するような場合は、リダイレクトを考慮して出力ストリームをしっかり意識し、標準出力と標準エラー出力を適切に使い分けるようにした方が良いです。
スポンサーリンク
まとめ
このページでは、標準出力・標準入力・標準エラー出力について、主にC言語の観点から解説しました!
これらはストリームであり、他のストリームと違ってプロセス用(実行中のプログラム用)にあらかじめ確立されているストリームとなります。
これらのストリームに対して入力や出力を行うことで、そのストリームの接続先に対して入力や出力を行うことができます。
関数等に利用するストリームとして標準出力・標準入力・標準エラー出力を指定する場合は、下記のマクロを利用します。
stdout
:標準出力stdin
:標準入力stderr
:標準エラー出力
この標準出力・標準入力・標準エラー出力はリダイレクトによって接続先を変更することも可能です。特にリダイレクトと絡めて考えると、標準出力と標準エラー出力の違いや使い分けの重要性も理解できると思います。
もちろん自分だけが利用するプログラムの場合はそこまで神経質になる必要もないですが、他の人に提供するようなプログラムを開発する場合は、標準出力と標準エラー出力を区別して適切に出力するようにしておくのが良いと思います。
オススメの参考書(PR)
C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!
まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。
- 参考書によって、解説の仕方は異なる
- 読み手によって、理解しやすい解説の仕方は異なる
ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?
それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。
なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。
特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。
もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!
入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/