Mengapa printf tidak hilang setelah panggilan kecuali jika baris baru dalam format string?

539

Mengapa printftidak disiram setelah panggilan kecuali baris baru di string format? Apakah ini perilaku POSIX? Bagaimana mungkin saya printfsegera memerah setiap kali?

Chenz gila
sumber
2
apakah Anda menyelidiki apakah ini terjadi pada file apa pun atau hanya dengan terminal? yang akan terdengar menjadi fitur terminal pintar untuk tidak line output yang belum selesai dari program latar belakang, meskipun saya berharap itu tidak akan berlaku untuk yang program latar depan.
PypeBros
7
Di bawah bash Cygwin Saya melihat perilaku yang sama bahkan jika baris baru adalah dalam format string. Masalah ini baru pada Windows 7; kode sumber yang sama berfungsi dengan baik pada Windows XP. MS cmd.exe memerah seperti yang diharapkan. Perbaikan setvbuf(stdout, (char*)NULL, _IONBF, 0)dapat mengatasi masalah, tetapi tentunya tidak perlu dilakukan. Saya menggunakan MSVC ++ 2008 Express. ~~~
Steve Pitchers
9
Untuk memperjelas judul pertanyaan: printf(..) tidak melakukan pembilasan sendiri, itu adalah buffering stdoutyang mungkin memerah ketika melihat baris baru (jika baris-buffered). Ini akan bereaksi dengan cara yang sama putchar('\n');, jadi printf(..)tidak istimewa dalam hal ini. Ini berbeda dengan cout << endl;, dokumentasi yang secara jelas menyebutkan pembilasan. The dokumentasi printf tidak menyebutkan pembilasan sekali.
Evgeni Sergeev
1
tulisan (/ pembilasan) berpotensi operasi mahal, mungkin buffered karena alasan kinerja
hanshenrik
@ EvgeniSergeev: Apakah ada konsensus bahwa pertanyaannya telah salah mendiagnosis masalah, dan bahwa pembilasan terjadi ketika ada baris baru di output ? (Menempatkan satu di string format adalah satu cara, tetapi bukan satu-satunya cara, untuk mendapatkan satu di output).
Ben Voigt

Jawaban:

703

The stdoutstream baris buffered secara default, sehingga hanya akan menampilkan apa yang ada di buffer setelah mencapai baris baru (atau ketika itu disuruh). Anda memiliki beberapa opsi untuk segera dicetak:

Cetak untuk stderrmenggunakan fprintf( stderrtidak diblokir secara default ):

fprintf(stderr, "I will be printed immediately");

Siram stdout kapan pun Anda membutuhkannya untuk menggunakan fflush:

printf("Buffered, will be flushed");
fflush(stdout); // Will now print everything in the stdout buffer

Sunting : Dari komentar Andy Ross di bawah ini, Anda juga dapat menonaktifkan buffering di stdout dengan menggunakan setbuf:

setbuf(stdout, NULL);

atau versi setvbufamannya seperti dijelaskan di sini

setvbuf(stdout, NULL, _IONBF, 0); 
Rudd Zwolinski
sumber
266
Atau, untuk menonaktifkan buffering sepenuhnya:setbuf(stdout, NULL);
Andy Ross
80
Juga, hanya ingin menyebutkan bahwa ternyata di UNIX baris baru biasanya hanya akan menyiram buffer jika stdout adalah terminal. Jika output sedang diarahkan ke file, baris baru tidak akan memerah.
hora
5
Saya merasa bahwa saya harus menambahkan: Saya baru saja menguji teori ini, dan saya menemukan bahwa menggunakan setlinebuf()pada aliran yang tidak diarahkan ke terminal adalah pembilasan pada akhir setiap baris.
Doddy
8
"Seperti yang awalnya dibuka, aliran kesalahan standar tidak sepenuhnya disangga; input standar dan aliran keluaran standar sepenuhnya disangga jika dan hanya jika aliran dapat ditentukan untuk tidak merujuk ke perangkat interaktif" - lihat pertanyaan ini: stackoverflow.com / pertanyaan / 5229096 / ...
Seppo Enarvi
3
@RuddZwolinski Jika ini akan menjadi jawaban kanon yang bagus untuk "mengapa tidak dicetak" sepertinya penting untuk menyebutkan perbedaan terminal / file sesuai "Apakah printf selalu menyiram buffer saat menemukan baris baru?" langsung dalam jawaban yang sangat tervvotasikan ini, vs orang-orang yang perlu membaca komentar ...
HostileFork mengatakan jangan percaya SE
128

Tidak, ini bukan perilaku POSIX, itu perilaku ISO (well, itu adalah perilaku POSIX tetapi hanya sejauh mereka sesuai dengan ISO).

Output standar adalah buffer line jika dapat dideteksi untuk merujuk ke perangkat interaktif, jika tidak buffered sepenuhnya. Jadi ada situasi di mana printftidak akan memerah, bahkan jika mendapat baris baru untuk dikirim, seperti:

myprog >myfile.txt

Ini masuk akal untuk efisiensi karena, jika Anda berinteraksi dengan pengguna, mereka mungkin ingin melihat setiap baris. Jika Anda mengirim output ke file, kemungkinan besar tidak ada pengguna di ujung yang lain (meskipun bukan tidak mungkin, mereka mungkin mengekor file). Sekarang Anda dapat berdebat bahwa pengguna ingin melihat setiap karakter tetapi ada dua masalah dengan itu.

Yang pertama adalah tidak efisien. Yang kedua adalah bahwa mandat ANSI C asli adalah untuk mengkodifikasi perilaku yang sudah ada , daripada menciptakan perilaku baru , dan keputusan desain dibuat jauh sebelum ANSI memulai proses. Bahkan ISO saat ini melangkah dengan sangat hati-hati ketika mengubah aturan yang ada dalam standar.

Bagaimana cara menghadapinya, jika Anda fflush (stdout)setelah setiap panggilan panggilan yang ingin Anda lihat segera, itu akan menyelesaikan masalah.

Sebagai alternatif, Anda dapat menggunakan setvbufsebelum beroperasi pada stdout, untuk mengaturnya untuk tidak dibangun dan Anda tidak perlu khawatir menambahkan semua fflushbaris ke kode Anda:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Hanya perlu diingat bahwa dapat mempengaruhi kinerja cukup sedikit jika Anda sedang mengirim output ke file. Juga perlu diingat bahwa dukungan untuk ini adalah implementasi yang ditentukan, tidak dijamin oleh standar.

Bagian ISO C99 7.19.3/3adalah bit yang relevan:

Ketika sungai adalah unbuffered , karakter dimaksudkan untuk muncul dari sumber atau di tempat tujuan secepat mungkin. Kalau tidak, karakter dapat diakumulasikan dan dikirim ke atau dari lingkungan host sebagai blok.

Ketika aliran sepenuhnya buffered , karakter dimaksudkan untuk dikirim ke atau dari lingkungan host sebagai blok ketika buffer diisi.

Ketika aliran buffer line , karakter dimaksudkan untuk dikirim ke atau dari lingkungan host sebagai blok ketika karakter baris baru ditemui.

Selain itu, karakter dimaksudkan untuk ditransmisikan sebagai blok ke lingkungan host ketika buffer diisi, ketika input diminta pada stream yang tidak dikonstruksikan, atau ketika input diminta pada jalur buffered stream yang membutuhkan transmisi karakter dari lingkungan host .

Dukungan untuk karakteristik ini ditentukan oleh implementasi, dan dapat dipengaruhi melalui setbufdan setvbuffungsinya.

paxdiablo
sumber
8
Saya baru saja menemukan skenario di mana bahkan ada '\ n', printf () tidak memerah. Itu diatasi dengan menambahkan fflush (stdout), seperti yang Anda sebutkan di sini. Tapi saya bertanya-tanya alasan mengapa '\ n' gagal menyiram buffer di printf ().
Qiang Xu
11
@QiangXu, output standar hanya buffered dalam kasus di mana ia dapat ditentukan secara pasti untuk merujuk ke perangkat interaktif. Jadi, misalnya, jika Anda mengarahkan output myprog >/tmp/tmpfile, itu buffered penuh daripada buffered garis. Dari memori, penentuan apakah output standar Anda interaktif dibiarkan diterapkan.
paxdiablo
3
selanjutnya pada panggilan Windows setvbuf (...., _IOLBF) tidak akan berfungsi karena _IOLBF sama dengan _IOFBF di sana: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz
28

Mungkin seperti itu karena efisiensi dan karena jika Anda memiliki banyak program menulis ke TTY tunggal, dengan cara ini Anda tidak mendapatkan karakter pada satu baris yang saling terkait. Jadi jika program A dan B menghasilkan, biasanya Anda akan mendapatkan:

program A output
program B output
program B output
program A output
program B output

Ini bau, tetapi lebih baik daripada

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

Perhatikan bahwa itu bahkan tidak dijamin untuk menyiram pada baris baru, jadi Anda harus menyiram secara eksplisit jika memerah penting bagi Anda.

Perhotelan Selatan
sumber
26

Untuk segera flush call fflush(stdout)atau fflush(NULL)( NULLberarti flush semuanya).

Harun
sumber
31
Perlu diingat fflush(NULL);biasanya ide yang sangat buruk. Ini akan mematikan kinerja jika Anda memiliki banyak file terbuka, terutama di lingkungan multi-utas di mana Anda akan berjuang dengan segalanya untuk kunci.
R .. GitHub BERHENTI MEMBANTU ICE
14

Catatan: Perpustakaan runtime Microsoft tidak mendukung buffer garis, jadi printf("will print immediately to terminal"):

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setvbuf

Renato
sumber
3
Lebih buruk daripada printflangsung pergi ke terminal dalam kasus "normal" adalah fakta bahwa printfdan fprintfmendapatkan buffered lebih kasar bahkan dalam kasus di mana output mereka langsung digunakan. Kecuali MS telah memperbaiki hal-hal, yang membuat mustahil satu program untuk menangkap stderr dan stdout dari yang lain dan mengidentifikasi dalam urutan apa barang dikirim ke masing-masing.
supercat
tidak, itu tidak langsung mencetak ke terminal kecuali tidak ada buffering ditetapkan. Secara default buffering penuh digunakan
phuclv
12

stdout di-buffered, jadi hanya akan keluar setelah baris baru dicetak.

Untuk mendapatkan hasil langsung, baik:

  1. Cetak ke stderr.
  2. Buat stdout tidak bermasalah.
Douglas Leeder
sumber
10
Atau fflush(stdout).
RastaJedi
2
"jadi hanya akan keluar setelah baris baru dicetak." Bukan hanya ini tetapi setidaknya 4 kasus lainnya. buffer penuh, tulis ke stderr(jawaban ini menyebutkan nanti) fflush(stdout),, fflush(NULL).
chux - Reinstate Monica
11

secara default, stdout adalah buffered baris, stderr tidak ada buffered dan file sepenuhnya buffered.

woso
sumber
10

Anda dapat menggunakan fprintf to stderr, yang tidak dibuat-buat. Atau Anda dapat menyiram stdout saat Anda mau. Atau Anda dapat mengatur stdout ke unbuffered.

Rasmus Kaj
sumber
10

Gunakan setbuf(stdout, NULL);untuk menonaktifkan buffering.

dnahc araknayirp
sumber
2

Pada umumnya ada 2 level buffering-

1. Cache buffer Cache (membuat baca / tulis lebih cepat)

2. Buffer di perpustakaan I / O (mengurangi no. Panggilan sistem)

Mari kita ambil contoh fprintf and write().

Saat Anda menelepon fprintf(), itu tidak langsung menuju file. Pertama-tama pergi ke buffer stdio di memori program. Dari sana ditulis ke cache buffer kernel dengan menggunakan panggilan sistem tulis. Jadi salah satu cara untuk melewati buffer I / O secara langsung menggunakan write (). Cara lain adalah dengan menggunakan setbuff(stream,NULL). Ini mengatur mode buffering ke no buffering dan data langsung ditulis ke buffer kernel. Untuk secara paksa membuat data untuk dipindahkan ke buffer kernel, kita dapat menggunakan "\ n", yang dalam hal mode buffering default 'line buffering', akan menyiram buffer I / O. Atau bisa kita gunakan fflush(FILE *stream).

Sekarang kita berada di buffer kernel. Kernel (/ OS) ingin meminimalkan waktu akses disk dan karenanya hanya membaca / menulis blok disk. Jadi ketika sebuah read()dikeluarkan, yang merupakan panggilan sistem dan dapat dipanggil secara langsung atau melalui fscanf(), kernel membaca blok disk dari disk dan menyimpannya dalam buffer. Setelah itu data disalin dari sini ke ruang pengguna.

Demikian pula, fprintf()data yang diterima dari buffer I / O ditulis ke disk oleh kernel. Ini membuat read () write () lebih cepat.

Sekarang untuk memaksa kernel untuk memulai write(), setelah itu transfer data dikendalikan oleh pengontrol perangkat keras, ada juga beberapa cara. Kami dapat menggunakan O_SYNCatau bendera serupa selama panggilan tulis. Atau kita bisa menggunakan fungsi lain seperti fsync(),fdatasync(),sync()membuat kernel inisiat menulis segera setelah data tersedia di buffer kernel.

o_O
sumber