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:
| Mode | Konstanta | Kapan buffer dikirimkan ke OS | Stream default |
|---|---|---|---|
| Line-buffered | _IOLBF | Saat bertemu karakter \n | stdin, stdout (terminal) |
| Full-buffered | _IOFBF | Saat buffer penuh atau dipaksa fflush | stdout (file/pipe), stdin (file) |
| Unbuffered | _IONBF | Langsung, tanpa buffer | stderr |
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:
#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):
[1] mulai stderr
[3] stderr lagi
[2] pesan stdout
[4] stdout lagiOutput yang Dihasilkan saat stdout dan stderr dipisahkan ke berkas berbeda:
Isi /tmp/buf_stdout.txt:
[2] pesan stdout
[4] stdout lagi
Isi /tmp/buf_stderr.txt:
[1] mulai stderr
[3] stderr lagiMari kita telaah baris-baris kode di atas:
- Baris 5 (
fprintf(stderr, "[1] mulai stderr\n");): pesan kestderrlangsung dikirim ke OS karenastderrtidak di-buffer. Pesan muncul di terminal segera. - Baris 7 (
fprintf(stdout, "[2] pesan stdout\n");): pesan kestdoutditahan di buffer. Saat output dipipakan kecat,stdoutberubah menjadi full-buffered (bukan line-buffered), sehingga meskipun ada\n, pesan tetap ditahan di buffer sampai buffer penuh atau program berakhir. - Baris 9 (
fprintf(stderr, "[3] stderr lagi\n");): pesan kedua kestderrlangsung muncul, sebelum pesan[2]yang masih ditahan di bufferstdout. Inilah mengapa urutan di output tercampur:[1],[3], lalu baru[2],[4]saat bufferstdoutakhirnya dikirim saat program berakhir. - Baris 11 (
fprintf(stdout, "[4] stdout lagi\n");): pesan keempat ditambahkan ke bufferstdout. Seluruh buffer dikirim saat program keluar melalui prosesexit, yang memanggilfflushotomatis 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.
#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):
Memulai proses...Setelah 1 detik, baris tersebut diperbarui menjadi:
Memulai proses... (1 detik kemudian)
Selesai.Mari kita telaah baris-baris kode di atas:
- Baris 6 (
printf("Memulai proses...");): mencetak teks tanpa\n. Karenastdoutdefaultnya line-buffered saat terhubung ke terminal, teks tetap di buffer sampai ada\nataufflush. - Baris 7 (
fflush(stdout);): memaksa pustaka C mengirim isi bufferstdoutke OS segera, meskipun belum ada\n. Tanpa baris ini, pengguna tidak akan melihat “Memulai proses…” sampai program selesai. - Baris 8 (
sleep(1);): menjeda program selama 1 detik. Selama jeda ini, pesan “Memulai proses…” sudah terlihat di terminal berkatfflushsebelumnya. - Baris 9 (
printf(" (1 detik kemudian)\n");): melanjutkan teks dengan\ndi akhir. Karena ada\n, buffer otomatis di-flush oleh mode line-buffered. - 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.
#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):
Tanpa newline, tanpa fflushSetelah 1 detik, baris tersebut diperbarui menjadi:
Tanpa newline, tanpa fflush (1 detik kemudian)
Selesai.Mari kita telaah baris-baris kode di atas:
- Baris 6 (
setvbuf(stdout, NULL, _IONBF, 0);): mengubah mode bufferingstdoutmenjadi_IONBF(unbuffered). Setelah pemanggilan ini, setiap karakter yang ditulis kestdoutlangsung dikirim ke OS tanpa menunggu buffer. - Argumen kedua
NULL: membiarkan pustaka C mengalokasikan buffer internal sendiri. Untuk mode unbuffered, buffer tidak digunakan. - Argumen keempat
0: ukuran buffer. Untuk mode unbuffered, ukuran diabaikan. - Baris 8 (
printf("Tanpa newline, tanpa fflush");): teks langsung muncul di terminal meskipun tanpa\ndan tanpafflush, karena mode buffering sudah diubah menjadi unbuffered. - Baris 10 (
printf(" (1 detik kemudian)\n");): sambungan teks muncul di baris yang sama karena tidak ada\ndi 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:
| Stream | Terminal (TTY) | Pipe atau file | Setelah fflush | Setelah setvbuf(_IONBF) |
|---|---|---|---|---|
stdout | Line-buffered (per \n) | Full-buffered (per buffer penuh) | Langsung | Langsung |
stderr | Unbuffered | Unbuffered | Langsung | Langsung |
stdin | Line-buffered | Full-buffered | Tidak relevan (input) | Tidak relevan |
Implikasi praktis untuk debugging:
- Jika kita memakai
printftanpa\nuntuk pesan progress dan program kita crash sebelum\nberikutnya, pesan tersebut tidak akan tercetak karena buffer belum dikirim ke OS. Gunakanfprintf(stderr, ...)ataufflush(stdout)untuk pesan debugging. - Jika kita menulis log panjang ke berkas dengan
fprintf(stdout, ...)dan ingin memastikan log tersimpan walau program crash, panggilfflush(stdout)secara periodik atau ubah ke mode line-buffered dengansetvbuf(stdout, NULL, _IOLBF, 0).