Anda salah menggabungkan fungsi IO buffered dan unbuffered. Kombinasi seperti itu harus dilakukan dengan sangat hati-hati terutama ketika kode harus portabel. (dan menulis kode yang tidak dapat dibawa-bawa adalah hal yang buruk...)
Yang terbaik adalah menghindari penggabungan IO buffered dan unbuffered pada deskriptor file yang sama.
IO yang disangga: fprintf()
, fopen()
, fclose()
, freopen()
...
IO tanpa buffer: write()
, open()
, close()
, dup()
...
Saat Anda menggunakan dup2()
untuk mengarahkan ulang stdout. Fungsi tidak mengetahui buffer yang diisi oleh fprintf()
. Jadi ketika dup2()
menutup deskriptor lama 1 tidak menyiram buffer dan konten dapat dibuang ke keluaran yang berbeda. Dalam kasus Anda 2a, itu dikirim ke /dev/null
.
Solusinya
Dalam kasus Anda, yang terbaik adalah menggunakan freopen()
bukannya dup2()
. Ini menyelesaikan semua masalah Anda:
- Ini menghapus buffer dari
FILE
asli sungai kecil. (kasus 2a) - Menyetel mode buffering sesuai dengan file yang baru dibuka. (kasus 3)
Inilah implementasi yang benar dari fungsi Anda:
void RedirectStdout2File(const char* log_path) {
if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}
Sayangnya dengan buffer IO Anda tidak dapat langsung mengatur izin dari file yang baru dibuat. Anda harus menggunakan panggilan lain untuk mengubah izin atau Anda dapat menggunakan ekstensi glibc yang tidak dapat dipindahkan. Lihat fopen() man page
.
Pembilasan untuk stdout
ditentukan oleh perilaku buffering-nya. Buffering dapat diatur ke tiga mode:_IOFBF
(penyangga penuh:menunggu hingga fflush()
jika memungkinkan), _IOLBF
(penyangga baris:baris baru memicu flush otomatis), dan _IONBF
(tulis langsung selalu digunakan). "Dukungan untuk karakteristik ini ditentukan oleh implementasi, dan dapat dipengaruhi melalui setbuf()
dan setvbuf()
fungsi." [C99:7.19.3.3]
"Pada permulaan program, tiga aliran teks telah ditentukan sebelumnya dan tidak perlu dibuka secara eksplisit—input standar (untuk membaca input konvensional), output standar (untuk menulis output konvensional), dan kesalahan standar (untuk menulis output diagnostik). Seperti yang awalnya dibuka, kesalahan standar stream tidak sepenuhnya disangga; input standar dan aliran output standar sepenuhnya disangga jika dan hanya jika aliran dapat ditentukan untuk tidak merujuk ke perangkat interaktif." [C99:7.19.3.7]
Penjelasan perilaku yang diamati
Jadi, yang terjadi adalah implementasi melakukan sesuatu yang khusus platform untuk memutuskan apakah stdout
akan menjadi garis-buffer. Di sebagian besar implementasi libc, pengujian ini dilakukan saat aliran pertama kali digunakan.
- Perilaku #1 mudah dijelaskan:saat streaming adalah untuk perangkat interaktif, ini adalah buffer baris, dan
printf()
memerah secara otomatis. - Kasus #2 sekarang juga diharapkan:saat kita mengalihkan ke file, aliran sepenuhnya disangga dan tidak akan dibilas kecuali dengan
fflush()
, kecuali jika Anda menulis gobloads data ke dalamnya. - Akhirnya, kami memahami kasus #3 juga untuk implementasi yang hanya melakukan pemeriksaan pada fd yang mendasarinya satu kali. Karena kami memaksa buffer stdout untuk diinisialisasi di
printf()
pertama , stdout memperoleh mode buffer garis. Saat kami menukar fd untuk membuka file, itu masih buffer baris, jadi datanya dihapus secara otomatis.
Beberapa implementasi aktual
Setiap libc memiliki kebebasan dalam menginterpretasikan persyaratan ini, karena C99 tidak menentukan apa itu "perangkat interaktif", entri stdio POSIX juga tidak memperluas ini (lebih dari sekadar mengharuskan stderr terbuka untuk membaca).
-
Glibc. Lihat filedoalloc.c:L111. Di sini kita menggunakan
stat()
untuk menguji apakah fd adalah tty, dan atur mode buffering yang sesuai. (Ini dipanggil dari fileops.c.)stdout
awalnya memiliki buffer null, dan dialokasikan pada penggunaan pertama aliran berdasarkan karakteristik fd 1. -
Lib BSD. Sangat mirip, tetapi kode yang jauh lebih bersih untuk diikuti! Lihat baris ini di makebuf.c