見出し画像

C言語 writeとprintf くらべてみました

こんにちは!  42tokyo Advent Calendar 2020の19日目を担当する、42tokyo生徒のkawatakuです!
前日18日目はふじさんによる League of Legendsのすすめ でした!新しくゲームを始めたい方はぜひ参考に!


このnoteではC言語におけるwriteとprintfの違い、主にprintfのバッファリングについて私なりに情報をまとめて検証してみたのでシェアします!!

急ぐ方へ結論から述べると、 writeは受け取った文字列をすぐに出力するのに対し、printfは行バッファリングを行い、文字列を詰め込んだバッファに改行が入る、または既定のバッファサイズを超えた時点で出力するようです。(以下は検証です)

モチベーション

この記事を書くことになったきっかけは、writeとprintfが混在するプログラムのデバッグ中に期待していない出力に遭遇したことです。簡略に再現すると以下のようになります。​

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
	char *abc = "abcdef";
	char *num = "123456";
	for (int i = 0; i < strlen(num); i++)
	{
		for (int j = 0; j < strlen(abc); j++)
		{
			write(1,&abc[j],1);
		}
		printf("%c",num[i]);
	}
	return 0;
}

//OUTPUT
//abcdefabcdefabcdefabcdefabcdefabcdef123456

期待していた出力は
abcdef1abcdef2abcdef3abcdef4abcdef5abcdef6
でしたが、実際はwriteが優先して実行されその後にprintfが実行されたような出力になりました。

調べてみるとprintf(C言語)はストリームに対してバッファリングを行い、ある決まったタイミングで出力するようです。

3種類あるバッファリングモード

"C言語はストリーム (stream) に対して,以下の 3 種類のモードのバッファを設定することが出来ます.
•行バッファリング (line buffered): 改行文字に達した時点でデータを送り出す
•完全バッファリング (fully buffered): バッファが一杯になった時点でデータを送り出す
•バッファリングなし (unbuffered): バッファリングしない"  引用

C言語では3種類のバッファリングモードが用意されているようです。(そもそもバッファリングとは?) *バッファリング


ここから、バッファリングのモードとバッファサイズを変更できるsetvbuf関数を用いて3種類のバッファリングの出力の違いを見ていきます。 

setvbuf関数

int setvbuf(FILE* stream, char* buf, int mode, size_t size);
// stdio.h にてバッファリングモードが以下のように宣言されています
#define	_IOFBF	0		/* setvbuf should set fully buffered */
#define	_IOLBF	1		/* setvbuf should set line buffered */
#define	_IONBF	2		/* setvbuf should set unbuffered */

•完全バッファリング (fully buffered)

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
    //stdoutに対する出力に バッファサイズを2,モードを完全バッファリング に設定
	static char stdoutBuf[2];
	if(setvbuf(stdout, stdoutBuf, _IOFBF, sizeof(stdoutBuf)) != 0)
    {
       perror("ERROR\n");
		return 0;
    }

	char *abc = "abcdef";
	char *num = "123456";
	for (int i = 0; i < strlen(num); i++)
	{
		for (int j = 0; j < strlen(abc); j++)
		{
			write(1,&abc[j],1);
		}
		printf("%c",num[i]);
	}
	return 0;
}

//OUTPUT:
//abcdefabcdefabcdef12abcdefabcdef34abcdef56

出力を見るとバッファに文字を詰めて行き、指定したバッファのサイズを超えたところでprintfが出力しているのが確認できます。


•行バッファリング (line buffered)

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
   //stdoutに対する出力に バッファサイズを10,モードを行バッファリング に設定
	static char stdoutBuf[10];
	if(setvbuf(stdout, stdoutBuf, _IOLBF, sizeof(stdoutBuf)) != 0)    
    {
      perror("ERROR\n");
		return 0;
    }

	char *abc = "abcdef";
	char *num = "1\n23\n456";
	for (int i = 0; i < strlen(num); i++)
	{
		for (int j = 0; j < strlen(abc); j++)
		{
			write(1,&abc[j],1);
		}
		printf("%c",num[i]);
	}
	return 0;
}

//OUTPUT:
//abcdefabcdef1
//abcdefabcdefabcdef23
//abcdefabcdefabcdef456

改行のたびに出力されています。
また、行バッファリングはバッファに改行がなくても、指定したバッファサイズを超えたタイミングでも出力します。

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
   //stdoutに対する出力に バッファサイズを2,モードを行バッファリング に設定
	static char stdoutBuf[2];
	if(setvbuf(stdout, stdoutBuf, _IOLBF, sizeof(stdoutBuf)) != 0)    
    {
      perror("ERROR\n");
		return 0;
    }

	char *abc = "abcdef";
	char *num = "123\n456";
	for (int i = 0; i < strlen(num); i++)
	{
		for (int j = 0; j < strlen(abc); j++)
		{
			write(1,&abc[j],1);
		}
		printf("%c",num[i]);
	}
	return 0;
}
//OUTPUT:
//abcdefabcdefabcdef12abcdef3
//abcdefabcdefabcdef456


•バッファリングなし (unbuffered)

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
    //stdoutに対する出力に バッファサイズを10,モードをバッファなし に設定
	static char stdoutBuf[10];
	if(setvbuf(stdout, stdoutBuf, _IONBF, sizeof(stdoutBuf)) != 0)
    {
       perror("ERROR\n");
		return 0;
    }

	char *abc = "abcdef";
	char *num = "123456";
	for (int i = 0; i < strlen(num); i++)
	{
		for (int j = 0; j < strlen(abc); j++)
		{
			write(1,&abc[j],1);
		}
		printf("%c",num[i]);
	}
	return 0;
}

//OUTPUT
//abcdef1abcdef2abcdef3abcdef4abcdef5abcdef6

printfがバッファに保持することなく、受け取った時点で出力しています。

まとめ

以上が3つのバッファリングの違いの検証になります!!
なので、私が最初に行ったテストの出力は何もおかしくなく、"123456" がバッファに格納され最後に出力されていただけでした!

デフォルトのバッファリングモードは行バッファリングです。また、バッファサイズは多くの場合4096byteのようです。(Stdout Buffering) 


最後まで読んでいただきありがとうございます!!

明日のアドベントカレンダーはケチゴンさんによる「自作言語(コンパイラ作成)の進め方」です!!



引用•参考記事
バッファリング
http://www.c-tipsref.com/words/buffering.html
http://x68000.q-e-d.net/~68user/webcgi/buffering.html
https://programming-place.net/ppp/contents/c/043.html
https://eklitzke.org/stdout-buffering
setvbuf
https://docs.microsoft.com/ja-jp/cpp/c-runtime-library/reference/setvbuf?view=msvc-160


この記事が気に入ったらサポートをしてみませんか?