Setiap kali kita memanggil printf, output tidak selalu langsung sampai ke layar. Untuk efisiensi, pustaka C standar menyimpan output sementara di sebuah buffer di memori, lalu mengirimkannya ke sistem operasi dalam batch. Mekanisme ini disebut buffering, dan pemahaman tentangnya menjawab banyak misteri: mengapa pesan “Loading…” tidak muncul sampai program selesai, mengapa urutan output stdout dan stderr berantakan saat dipipakan, serta kapan kita harus memanggil fflush. Pada chapter ini kita akan mempelajari tiga mode buffering (line-buffered, full-buffered, unbuffered), cara mengubahnya dengan setvbuf, serta demonstrasi konkret perbedaan perilakunya.

Tiga Mode Buffering #

Pustaka <stdio.h> mengenal tiga mode buffering yang ditentukan oleh konstanta berikut:

ModeKonstantaKapan buffer dikirimkan ke OSStream default
Line-buffered_IOLBFSaat bertemu karakter \nstdin, stdout (terminal)
Full-buffered_IOFBFSaat buffer penuh atau dipaksa fflushstdout (file/pipe), stdin (file)
Unbuffered_IONBFLangsung, tanpa bufferstderr

Perbedaan mode inilah yang menyebabkan printf tanpa \n terlihat “menggantung” di terminal, sementara fprintf(stderr, ...) langsung muncul. Berikut contoh yang menunjukkan perbedaan urutan output stdout versus stderr saat dipipakan:

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

int main(void) {
    fprintf(stderr, "[1] mulai stderr\n");
    sleep(1);
    fprintf(stdout, "[2] pesan stdout\n");
    sleep(1);
    fprintf(stderr, "[3] stderr lagi\n");
    sleep(1);
    fprintf(stdout, "[4] stdout lagi\n");
    return 0;
}

Output yang Dihasilkan (dipipakan ke cat, urutan kronologis tercampur):

text
[1] mulai stderr
[3] stderr lagi
[2] pesan stdout
[4] stdout lagi

Output yang Dihasilkan saat stdout dan stderr dipisahkan ke berkas berbeda:

text
Isi /tmp/buf_stdout.txt:
[2] pesan stdout
[4] stdout lagi

Isi /tmp/buf_stderr.txt:
[1] mulai stderr
[3] stderr lagi

Mari kita telaah baris-baris kode di atas:

  1. Baris 5 (fprintf(stderr, "[1] mulai stderr\n");): pesan ke stderr langsung dikirim ke OS karena stderr tidak di-buffer. Pesan muncul di terminal segera.
  2. Baris 7 (fprintf(stdout, "[2] pesan stdout\n");): pesan ke stdout ditahan di buffer. Saat output dipipakan ke cat, stdout berubah menjadi full-buffered (bukan line-buffered), sehingga meskipun ada \n, pesan tetap ditahan di buffer sampai buffer penuh atau program berakhir.
  3. Baris 9 (fprintf(stderr, "[3] stderr lagi\n");): pesan kedua ke stderr langsung muncul, sebelum pesan [2] yang masih ditahan di buffer stdout. Inilah mengapa urutan di output tercampur: [1], [3], lalu baru [2], [4] saat buffer stdout akhirnya dikirim saat program berakhir.
  4. Baris 11 (fprintf(stdout, "[4] stdout lagi\n");): pesan keempat ditambahkan ke buffer stdout. Seluruh buffer dikirim saat program keluar melalui proses exit, yang memanggil fflush otomatis ke semua stream output.

Memaksa flush dengan fflush #

Jika kita ingin output muncul segera tanpa menunggu buffer penuh, kita bisa memanggil fflush(stream) yang langsung mengirim isi buffer ke sistem operasi. Fungsi ini berguna untuk pesan progress bar, indikator loading, atau log real-time yang harus terlihat oleh pengguna saat program sedang sibuk.

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

int main(void) {
    /* Tanpa newline: ditambahkan fflush agar segera tampil */
    printf("Memulai proses...");
    fflush(stdout);
    sleep(1);
    printf(" (1 detik kemudian)\n");
    printf("Selesai.\n");
    return 0;
}

Output yang Dihasilkan (saat dijalankan langsung di terminal, urutan kronologis):

text
Memulai proses...

Setelah 1 detik, baris tersebut diperbarui menjadi:

text
Memulai proses... (1 detik kemudian)
Selesai.

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (printf("Memulai proses...");): mencetak teks tanpa \n. Karena stdout defaultnya line-buffered saat terhubung ke terminal, teks tetap di buffer sampai ada \n atau fflush.
  2. Baris 7 (fflush(stdout);): memaksa pustaka C mengirim isi buffer stdout ke OS segera, meskipun belum ada \n. Tanpa baris ini, pengguna tidak akan melihat “Memulai proses…” sampai program selesai.
  3. Baris 8 (sleep(1);): menjeda program selama 1 detik. Selama jeda ini, pesan “Memulai proses…” sudah terlihat di terminal berkat fflush sebelumnya.
  4. Baris 9 (printf(" (1 detik kemudian)\n");): melanjutkan teks dengan \n di akhir. Karena ada \n, buffer otomatis di-flush oleh mode line-buffered.
  5. Baris 10 (printf("Selesai.\n");): pesan akhir yang juga langsung muncul karena diakhiri \n.

Mengubah Mode Buffering dengan setvbuf #

Fungsi setvbuf memberi kita kendali penuh atas mode buffering sebuah stream. Fungsi ini menerima empat argumen: stream, buffer kustom (boleh NULL), mode (_IONBF, _IOLBF, _IOFBF), dan ukuran buffer. Pemanggilan setvbuf harus dilakukan sebelum operasi I/O pertama pada stream tersebut.

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

int main(void) {
    /* Ubah mode buffering stdout menjadi unbuffered */
    setvbuf(stdout, NULL, _IONBF, 0);

    printf("Tanpa newline, tanpa fflush");
    sleep(1);
    printf(" (1 detik kemudian)\n");
    printf("Selesai.\n");
    return 0;
}

Output yang Dihasilkan (saat dijalankan langsung di terminal, urutan kronologis):

text
Tanpa newline, tanpa fflush

Setelah 1 detik, baris tersebut diperbarui menjadi:

text
Tanpa newline, tanpa fflush (1 detik kemudian)
Selesai.

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (setvbuf(stdout, NULL, _IONBF, 0);): mengubah mode buffering stdout menjadi _IONBF (unbuffered). Setelah pemanggilan ini, setiap karakter yang ditulis ke stdout langsung dikirim ke OS tanpa menunggu buffer.
  2. Argumen kedua NULL: membiarkan pustaka C mengalokasikan buffer internal sendiri. Untuk mode unbuffered, buffer tidak digunakan.
  3. Argumen keempat 0: ukuran buffer. Untuk mode unbuffered, ukuran diabaikan.
  4. Baris 8 (printf("Tanpa newline, tanpa fflush");): teks langsung muncul di terminal meskipun tanpa \n dan tanpa fflush, karena mode buffering sudah diubah menjadi unbuffered.
  5. Baris 10 (printf(" (1 detik kemudian)\n");): sambungan teks muncul di baris yang sama karena tidak ada \n di pesan pertama. Pesan kedua ditambahkan langsung tanpa delay dari buffering.

Membandingkan Perilaku di Terminal vs Pipe #

Tabel berikut merangkum kapan buffering aktif dan kapan output muncul:

StreamTerminal (TTY)Pipe atau fileSetelah fflushSetelah setvbuf(_IONBF)
stdoutLine-buffered (per \n)Full-buffered (per buffer penuh)LangsungLangsung
stderrUnbufferedUnbufferedLangsungLangsung
stdinLine-bufferedFull-bufferedTidak relevan (input)Tidak relevan

Implikasi praktis untuk debugging:

  • Jika kita memakai printf tanpa \n untuk pesan progress dan program kita crash sebelum \n berikutnya, pesan tersebut tidak akan tercetak karena buffer belum dikirim ke OS. Gunakan fprintf(stderr, ...) atau fflush(stdout) untuk pesan debugging.
  • Jika kita menulis log panjang ke berkas dengan fprintf(stdout, ...) dan ingin memastikan log tersimpan walau program crash, panggil fflush(stdout) secara periodik atau ubah ke mode line-buffered dengan setvbuf(stdout, NULL, _IOLBF, 0).