このページでは、C言語の標準ライブラリ関数である setvbuf
関数と setbuf
関数について解説を行います!
下記ページでの fflush
関数の解説において、プログラムとファイル・コンソール画面等の間にはストリームが存在し、そのストリームでデータがバッファリングされる(データが一時的に溜められる)ことを説明しています。
なぜストリームでデータを溜めることが出来るかというと、これはストリームがデータを溜めるためのメモリを持っているからです。このメモリのことをバッファと呼びます。このバッファにデータが溜められ、バッファが満杯になる等のタイミングでデータがファイルやコンソール画面等に出力される事になります。
上記ページで紹介している fflush
関数は、そのバッファ内のデータをファイル等に強制的に出力する関数となります。
今回紹介する setvbuf
関数と setbuf
関数は、このストリームのバッファの設定を行う関数になります。
この設定を行う事により、ストリームに溜められるデータのサイズを変更したり、ストリームでのデータのバッファリングの仕方(データの溜め方)を変更したりすることができるようになります。
Contents
setvbuf
関数
まずは setvbuf
関数について解説していきます。
前述の通り、setvbuf
関数はストリームのバッファの設定を行う関数であり、下記のように宣言されています。
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int type, size_t size);
setvbuf
関数でのバッファの設定(引数)
setvbuf
関数でのバッファの設定は、setvbuf
関数に指定する引数に応じて行われます。
ということで、ここでは setvbuf
関数の引数について解説していきます。
stream
setvbuf
関数の第1引数 stream
には、バッファの設定を行いたいストリームを指定します。
stream
への指定例として分かりやすいのが fopen
関数の返却値ですね。これによりプログラムとファイル間のストリームのバッファの設定を行うことができます。
また、標準入力・標準出力・標準エラー出力もストリームであり、これらのバッファの設定も行うことが可能です。これらのバッファの設定を行う際には、stream
に下記を指定します。
stdin
:標準入力stdout
:標準出力stderr
:標準エラー出力
例えば printf
関数は標準出力に文字を出力する関数ですので、stream
に stdout
を指定すれば、printf
関数実行時に使用されるバッファの設定を変更することができ、printf
関数による文字の表示の動作を変化させることができます。
type
順番前後しますが、setvbuf
関数の第3引数 type
には、ストリームでのバッファリングの仕方を指定します。
type
に指定できるパラメータは下記の3つであり、これらは stdio.h
をインクルードすることで使用できる定数マクロとなります。
_IOFBUF
:フルバッファリング- バッファが満杯になるまでバッファリングを行う
_IOLBUF
:ラインバッファリング- バッファに改行文字が入力されるまで or バッファが満杯になるまでバッファリングを行う
_IONBUF
:バッファリングなし
例えば _IOLBUF
を指定した場合は、バッファに改行文字が入力された or バッファが満杯になったタイミングでストリームの接続先にバッファからデータが吐き出され、その後はまたバッファの先頭からデータがバッファリングされていく事になります。
ただし、type
を指定したからといって、必ず指定した通りにバッファリングされるとは限らないので注意してください。
例えば type
に _IOFBUF
を指定していたとしても、途中で入力処理が行われたりすると、バッファが満杯になる前にバッファからデータが吐き出される事になります。
buf
setvbuf
関数の第2引数 buf
には、ストリームで使用するバッファの先頭アドレスを指定します。
これにより、第3引数 type
に _IONBUF
以外を指定している場合、ストリームへ出力されたデータが buf
で指定されたバッファにバッファリングされる事になります(第2引数 type
に _IONBUF
を指定している場合は、バッファへのバッファリングは行われません)。
buf
には、例えば配列の先頭アドレスや malloc
関数等で確保したメモリの先頭アドレスを指定します。
第3引数 type
に _IONBUF
以外を指定する場合、buf
に指定する配列やメモリのサイズは第4引数で指定する size
以上である必要があります。setvbuf
関数ではアドレス buf
〜 buf + size - 1
をバッファとみなしてバッファリングを行いますので、buf
に指定する配列やメモリのサイズが size
よりも小さい場合、バッファオーバーランが発生する事になります。
また、バッファリングなしに設定するために buf
に NULL
を指定するのは間違いなので注意してください。バッファリングなしの設定は、前述の通り第3引数 type
への _IONBUF
の指定により行う必要があります。
buf
に NULL
を指定した場合、第4引数 size
に応じたメモリが関数内部で確保され、そのメモリがバッファとして利用されるようになるはずです(環境によって動作が異なるかも)。
size
setvbuf
関数の第4引数 size
には、ストリームのバッファのサイズをバイト単位で指定します。
基本的には setvbuf
関数の第2引数に指定する配列やメモリのサイズを指定します(それらよりも小さなサイズでも問題ない)。
size
に 0
を指定した場合、第2引数 buf
で指定したバッファではなく、元々ストリームに対して用意されるバッファが使用される事になります。
ですので、例えばあるストリームに対してバッファリングの仕方(type
)だけ変更したいような場合は、第4引数 size
に 0
を指定してやれば良いです(第2引数も NULL
で良い)。
スポンサーリンク
setvbuf
関数の返却値
setvbuf
関数は成功時に 0
を、失敗時には 0
以外を返却します。
例えば、第3引数 type
に _IOFBUF
・_IOLBUF
・_IONBUF
以外を指定した場合などに setvbuf
関数は 0
以外を返却します。
setvbuf
関数の使用例と効果
続いて setvbuf
関数の使用例を示しながら setvbuf
関数の効果を紹介していきまます。
setvbuf
関数を使用しない場合の printf
の出力
まずは、setvbuf
関数を使用しない場合の printf
関数の動作を確認しておきたいと思います。
下記は、setvbuf
関数を使用せずに、500
ミリ秒毎に printf
関数を実行して数字を出力し、5文字出力するたびに改行を出力するプログラムとなります。
usleep
関数を使っているので Windows 環境ではコンパイルできないかもしれないですが、Windows 用のスリープ関数を利用すれば同様のプログラムは実現できるはずです。
#include <stdio.h>
#include <unistd.h>
int main(void) {
for (int i = 0; i < 20; i++) {
printf("%d", i);
if (i % 5 == 4) {
printf("\n");
}
usleep(500000);
}
printf("\n");
}
私の環境で実行した場合の実行結果は下のアニメのようになりました。
改行文字 \n
が printf
で出力される度に、そこまで printf
で出力していた数字が一度に表示されることが確認できると思います。
もしかしたら環境によっては動作が異なるかもしれませんが、少なくともこの動作は、私の環境におけるストリームのバッファのデフォルト設定での動作という事になります(バッファリングの仕方がラインバッファリングに設定されている)。
setvbuf
関数でのバッファリングの仕方の設定変更
ここからは setvbuf
関数を実行して setvbuf
関数の効果を確認していきたいと思います。
まずは下記のソースコードを利用して setvbuf
関数の効果を確認してみましょう!
setvbuf 関数を使用しない場合の printf の出力 の時と printf
の実行の仕方などは同じですが、今回は setvbuf
関数を実行しています。この例におけるポイントは、setvbuf
関数の第3引数 type
に _IOFBF
を指定している点になります。
#include <stdio.h>
#include <unistd.h>
int main(void) {
if(setvbuf(stdout, NULL, _IOFBF, 0) != 0 ){
printf("setvbuf error\n");
}
for (int i = 0; i < 20; i++) {
printf("%d", i);
if (i % 5 == 4) {
printf("\n");
}
usleep(500000);
}
printf("\n");
}
今回は、私の環境で実行した場合の実行結果は下のアニメのようになりました。
setvbuf 関数を使用しない場合の printf の出力 の時とは異なり、改行 \n
が printf
で出力されても数字は出力されず、最後(プログラム終了時)に一気に printf
で出力された数字や改行が表示されていることが確認できると思います。
このように printf
で出力される文字の表示のされ方が変化したのは、下記のように setvbuf
関数を実行しているからになります。
setvbuf(stdout, NULL, _IOFBF, 0)
第1引数 stream
に stdout
を指定しているため、上記の setvbuf
関数の実行では標準出力ストリームのバッファの設定が行われる事になります。printf
関数は標準出力への出力を行う関数ですので、printf
関数の出力先となるバッファの設定が行われる事になります。
さらに、第3引数 type
に _IOFBF
を指定しているため、バッファリングの仕方がフルバッファリングに設定されます。すなわち、バッファが満杯になるまでバッファリングされるようになったため、途中で改行文字がバッファに入力されたとしてもデータが吐き出されないように動作が変化する事になります。
上記のバッファの設定により、printf
関数の出力する文字に関わらずバッファが満杯になるまでデータが溜められるようになったため、printf
で出力される文字の表示のされ方が変化したというわけです。
ちなみに、上記のプログラムではバッファは結局満杯にならないので、プログラム終了時に溜められたデータが一気に吐き出されることになります。
setvbuf
関数でのバッファのサイズの設定変更
次は、下記のソースコードを利用して setvbuf
関数の効果を確認していきたいと思います。
setvbuf 関数でのバッファリングの仕方の設定変更 に比べて、setvbuf
関数実行時の第2引数と第4引数の指定を変更しています。
#include <stdio.h>
#include <unistd.h>
#define SIZE 2
int main(void) {
static char buffer[SIZE];
if(setvbuf(stdout, buffer, _IOFBF, sizeof(buffer)) != 0 ){
printf("setvbuf error\n");
}
for (int i = 0; i < 20; i++) {
printf("%d", i);
if (i % 5 == 4) {
printf("\n");
}
usleep(500000);
}
printf("\n");
}
今回は、私の環境で実行した場合の実行結果は下のアニメのようになりました。
setvbuf 関数を使用しない場合の printf の出力 や setvbuf 関数でのバッファリングの仕方の設定変更 の時とは異なり、今回は2文字(改行文字も含めて)ずつ表示が行われるようになったことが確認できると思います。
これは、setvbuf
の第4引数 size
の指定によりバッファのサイズが 2
バイトに設定されたからです(sizeof(buffer)
は 2
となる)。
なので、printf
関数で1バイトの文字を2つ出力するだけでバッファから画面に文字が吐き出される事になり、2文字ずつ文字が画面に表示される動作となります。
当然、2バイトのデータを溜める必要があるため、setvbuf
の第2引数 buf
には、サイズが2バイト以上となる配列やメモリの先頭アドレスを指定する必要があります。
今回は、配列 buffer
を第2引数 buf
に指定しており、配列 buffer
はサイズが2バイト(型が char
で要素数が 2
)であるため、2バイトのデータを溜め込むことが可能となります。
今回は2文字ずつ文字が表示される様子を示したかったのでバッファのサイズを小さくしましたが、もちろんもっと大きなバッファにデータを溜めるように設定することも可能ですし、逆に第3引数に _IONBF
を指定してバッファリングしないように設定することも可能です。
setvbuf
関数の注意点
setvbuf
関数の最後の解説として、setvbuf
関数使用時の注意点について説明しておきます。
実行するタイミング
setvbuf
関数使用時の注意点の1つ目は、setvbuf
関数の実行タイミングです。
setvbuf
関数は、第1引数 stream
で指定するストリームに対し「オープンされてから入出力等で使用されるまで」のタイミングで実行する必要があります。
例えば stream
に fopen
の返却値を指定するのであれば、fopen
実行してストリームをオープンしてから fwrite
・fread
・fprintf
等の関数によってストリームに対して入出力を行うまでの間のタイミングで setvbuf
関数を実行する必要があります。
そのため、fopen
の直後に setvbuf
関数を実行するのが無難だと思います。
また、標準入力・標準出力・標準エラー出力に関してはプログラム実行時に自動的にオープンされますので、stream
に stdout
・stdin
・stderr
を指定する場合は、最初に printf
や scanf
関数等を実行する前に setvbuf
関数を実行する必要があります。
そのため、この場合はプログラム開始直後に setvbuf
関数を実行するのが無難だと思います。
バッファの生存期間
setvbuf
関数使用時の注意点の2つ目は、setvbuf
関数の第2引数 buf
で指定するバッファの生存期間です。
setvbuf
関数の第2引数 buf
で指定するバッファは、setvbuf
関数実行後から第1引数 stream
で指定したストリームがクローズされるまでの期間で使用される可能性があります。
そのため、buf
に指定するバッファは、stream
に指定するストリームがクローズされるまで生存している必要があります。
具体例を示すと、下記は良くない setvbuf
関数の使用例となります。なぜなら、buf
に指定するバッファは setting
関数終了後に生存期間が終了して解放されてしまうからです。
解放後の配列やメモリは使用してはいけません。
#include <stdio.h>
void setting(void) {
char buffer[256];
if (setvbuf(stdout, buffer, _IOLBF, sizeof(buffer))) {
printf("setvbuf error\n");
}
}
void print(void) {
char data[1024];
for (int i = 0; i < 1024; i++) {
data[i] = i % 26 + 'A';
}
}
int main(void) {
setting();
printf("hello world!\n");
print();
printf("hello world!\n");
}
どのように変数がメモリ上に配置されるかにもよりますが、私の環境で上記を実行すると次のように Segmentation fault が発生してしまいました。解放後の配列がバッファに設定されているので、printf
が実行されると解放後の配列にデータを格納する事になり、メモリ破壊が発生してプログラムが正常に動作できなくなってしまいます。
% ./main.exe hello world! zsh: segmentation fault ./main.exe
特に stream
に stdout
・stdin
・stderr
を指定する場合、これらがクローズされるのはプログラム終了時ですので、プログラム終了時まで buf
に指定するバッファは生存していなければならない事になります。
そう考えると、buf
に指定するバッファに関しては、例えば下記を利用するのが良いと思います。
- グローバル変数の配列
static
ローカル変数の配列
これらの配列の生存期間はプログラム終了までとなりますので、前述の例のように途中で解放されてプログラムが異常終了してしまうような現象を防ぐことができます。
スポンサーリンク
setbuf
関数
続いて setbuf
関数について解説していきます。
setbuf
関数は setvbuf
関数の簡易版みたいなものですので、ここでも簡単にのみ解説させていただきます。
まず setbuf
関数は、setvbuf
関数同様にストリームのバッファの設定を行う関数で、下記のように宣言されています。
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
setbuf
関数の返却値は「なし」です。また上記の通り、setvbuf
関数と比べて引数が少なく、設定できることも少なくなっています。
setbuf
関数でのバッファの設定(引数)
setbuf
関数でも、setvbuf
関数同様に指定する引数に応じてバッファの設定が行われる事になります。
setbuf
関数で指定する引数は下記の通りになります。
stream
setbuf
関数の第1引数 stream
には、バッファの設定を行いたいストリームを指定します。
setvbuf
関数の第1引数 stream
と同じ引数となりますので、説明は省略します。
buf
setbuf
関数の第2引数 buf
には、ストリームで使用するバッファの先頭アドレスを指定します。
setvbuf
関数の第2引数も buf
ではあるのですが、指定の仕方等が若干異なるので注意してください。
まず、setbuf
関数の第2引数 buf
に NULL
を指定した場合、ストリームのバッファリングの仕方がノンバッファリングに設定されます。つまり、そのストリームではバッファリングされなくなります。
逆に第2引数 buf
に NULL
以外を指定した場合は、ストリームのバッファリングの仕方はおそらくフルバッファリングに設定されるはずです。
これらに対し、setvbuf
関数の場合は第2引数 buf
の指定によってバッファリングの仕方が変化することはありません。ここが setbuf
関数と setvbuf
関数とで異なるので注意してください。
また、setvbuf
関数の場合はバッファのサイズを第4引数 size
で指定できるのに対し、setbuf
関数の場合はバッファのサイズを指定することはできません。setbuf
関数では、バッファのサイズは BUFSIZE
バイトであることを前提にバッファの設定が行われます(BUFSIZE
は stdio.h
をインクルードすることで使用できる定数マクロ)。
したがって、setbuf
関数の場合、第2引数で指定する配列やメモリのサイズは BUFSIZE
バイト以上である必要があります。これよりもサイズが小さい場合、バッファオーバーランが発生する可能性があるので注意してください。
setbuf
関数の使用例
簡単に使用例も紹介しておきます。
下記は、setbuf
関数を利用してバッファリングの仕方をノンバッファリングに設定する例となります。
#include <stdio.h>
int main(void) {
static char buffer[BUFSIZ];
setbuf(stdout, NULL);
/* 略 */
}
動作例の紹介は省略させていただきますが、バッファリングの仕方がノンバッファリングに設定されるため、以降で実行する printf
では1文字ずつ即座に画面に出力される事になります。
スポンサーリンク
setbuf
関数の注意点
setbuf
関数においても setvbuf
関数と同じ注意点が挙げられます。詳細に関しては setvbuf 関数の注意点 を参照してください。
また、setbuf
関数の場合、第2引数の指定によってバッファリングの仕方が設定される事になります。この点は setvbuf
関数と異なるため注意してください。
setvbuf
関数と setbuf
関数の使いどころ
最後に setvbuf
関数と setbuf
関数の使いどころについて説明しておきます。
バッファリングしたくない場合
使いどころの1つ目は、ストリームのバッファリングをしたくない場合が挙げられます。
ストリームではバッファリングが行われるため、例えば printf
などの出力結果が画面に表示されるのが遅れます。そして、この遅れがあるためにデバッグしにくいというケースもあり得ます(ストリームにデータが溜まっている状態でプログラムが落ちてしまい、溜まっていたデータが出力されない等)。
そんな時に、setvbuf
関数や setbuf
関数を利用してバッファリングの仕方をノンバッファリングに変更することで、即座に printf
などの出力結果が画面に反映されるようになり、デバッグや動作確認をしやすくすることができます。
下記ページで紹介している fflush
関数でもストリームから強制的にデータを吐き出させることで同様のことを実現することは可能です。
ただ、fflush
関数の場合はストリームから強制的にデータを吐き出させたいタイミングで毎回 fflush
関数を実行する必要がありますので、そういったタイミングが多い場合はコーディングが大変です。
それに対し、setvbuf
関数や setbuf
関数に関しては、最初に一度実行するだけでストリームのノンバッファリングを実現することができます。ただ、fflush
関数に比べるとちょっと引数の指定がややこしいですかね…。
スポンサーリンク
パフォーマンスを向上させたい場合
また、大量に出力を行うようなプログラムであれば、バッファのサイズやバッファリングの仕方の変更によりプログラムのパフォーマンスを向上させられる可能性があります。
例えば標準出力のバッファのサイズが小さい場合、printf
が実行されるたびに画面への出力が行われて負荷が大きくなってしまい、パフォーマンスが低下する可能性があります。
そういった場合、バッファのサイズを大きくすることで画面への出力回数を減らし、パフォーマンスを向上することができる可能性があります。
ただし、これはデフォルトのバッファのサイズ(BUFSIZE
)が小さい時のみの話で、デフォルトのバッファのサイズが十分大きい場合はパフォーマンスはほぼ向上しないと思います。
また、標準出力のバッファリングの仕方がラインバッファリング or ノンバッファリングの場合、フルバッファリングに変更することで画面への出力回数を減らし、パフォーマンスを向上させることができる可能性があります。
ただ、パフォーマンスが求められるようなプログラムにおいて、そもそもパフォーマンスが低下するほど printf
を実行するのは実装上問題アリと考えて良いかもしれないですね…。
デバッグ時のみ printf
を行うようにしたり、不要な printf
を行わないよう検討してみても良いと思います。
まとめ
このページでは、C言語の標準ライブラリ関数である setvbuf
関数と setbuf
関数について解説しました!
これらの関数を利用することで、ストリームのバッファの設定を行うことができます。
なかなか使う機会は少ないかもしれませんが、printf
等の動作を変化させることのできるおもしろい関数だと思います。また、バッファリングさせずに printf
での文字出力を行いたいような場面に今後出会う可能性もありますので、こういった関数が存在することだけでも覚えておくと良いと思います!