いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

標準出力をアンバッファリングにする (setbuf, setvbuf)(C言語)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

大した情報でも無いのですが、たまに標準出力をアンバッファリングにしたいときがあります。

少しだけしか出力しないプロセスとか、出力が改行しないものとか。

そのような場合にデフォルトの状態(ttyの場合はラインバッファ(_IOLBF)、パイプなどではフルバッファ(_IOFBF))で実行するとプロセスが終わるまで出力が出ない場合があります。

そういうときは、setbufsetvbuf を使ってアンバッファリングにしてやると良いときがあります。

ついでに、glibc固有ですがバッファモードを調べるやり方もサンプルに載せてあります。

サンプル

app.c

#include <stdbool.h>
#include <stdio.h>
#include <stdio_ext.h> // glibcが提供する標準I/Oの拡張機能を定義したヘッダファイル (glibc固有)
#include <stdlib.h>
#include <unistd.h>

static void print_msg(const size_t count);

int main(int argc, char *argv[]) {
    // glibcはバッファを遅延割当するため、実際に出力が行われるまでバッファを割り当てない。
    // (ラインバッファやフルバッファの場合に、一度も出力していないとバッファサイズが0バイトで表示される)
    // 本サンプルではバッファサイズを見たいので、意図的に一度出力を行っておく。
    printf("[start]\n");

    bool use_stdbuf = (argc >= 2) ? true : false;
    if (use_stdbuf) {
        // 標準出力をアンバッファリングに設定
        setbuf(stdout, NULL);
    }

#ifdef __GLIBC__
    // バッファリング設定を確認
    {
        size_t buf_size = __fbufsize(stdout);
        printf("バッファサイズ: %zu bytes\n", buf_size);

        bool is_line_buffer = __flbf(stdout) != 0;
        printf("ラインバッファ?: %s\n", is_line_buffer ? "はい" : "いいえ");

        bool is_unbuffered = (buf_size <= 1 && !is_line_buffer);
        if (is_unbuffered) {
            printf("状態: アンバッファ (_IONBF)\n");
        } else if (is_line_buffer) {
            printf("状態: ラインバッファ (_IOLBF)\n");
        } else {
            printf("状態: フルバッファ (_IOFBF)\n");
        }
    }
#endif

    // 確認
    {
        const int count = 5;
        print_msg(count);
    }
}

static void print_msg(const size_t count) {
    for (size_t i = 0; i < count; i++) {
        //
        // バッファリング有りの場合、高確率で即座に表示されないことが多い.
        // アンバッファリングの場合、即座に表示される
        //
        // つまり、以下の場合だと大抵の環境では
        //   - バッファリング  : 5秒後にまとめて出力
        //   - アンバッファリング: 即座に出力
        // となる
        //
        fprintf(stdout, "[%ld]", i);
        sleep(1);
    }

    printf("\n");
}

Taskfile.yml

# https://taskfile.dev

version: '3'

vars:
  APP: app

env:
  CC: gcc
  CFLAGS: -g3 -O0 -std=c99 -Wall -Wextra -Wno-unused-parameter

tasks:
  default:
    cmds:
      - task: build
      - task: run
  build:
    cmds:
      - $CC $CFLAGS -o {{.APP}} {{.APP}}.c
  run:
    cmds:
      # 普通に実行
      - ./{{.APP}}
      - cmd: echo '---------------------------------'
        silent: true
      # stdbufを設定してアンバッファリングにして実行
      - ./{{.APP}} 1
      - cmd: echo '---------------------------------'
        silent: true
      - ./{{.APP}} | tee out.txt
      - cmd: echo '---------------------------------'
        silent: true
      - ./{{.APP}} 1 | tee out.txt

実行

$ task
task: [build] $CC $CFLAGS -o app app.c
task: [run] ./app
[start]
バッファサイズ: 1024 bytes
ラインバッファ?: はい
状態: ラインバッファ (_IOLBF)
[0][1][2][3][4]
---------------------------------
task: [run] ./app 1
[start]
バッファサイズ: 1 bytes
ラインバッファ?: いいえ
状態: アンバッファ (_IONBF)
[0][1][2][3][4]
---------------------------------
task: [run] ./app | tee out.txt
[start]
バッファサイズ: 4096 bytes
ラインバッファ?: いいえ
状態: フルバッファ (_IOFBF)
[0][1][2][3][4]
---------------------------------
task: [run] ./app 1 | tee out.txt
[start]
バッファサイズ: 1 bytes
ラインバッファ?: いいえ
状態: アンバッファ (_IONBF)
[0][1][2][3][4]

結果をテキストで見ると同じに見えますが、アンバッファリングを設定していない場合は、5秒後に一気に一行分出力されます。

参考情報


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。