Biçim dizesinde yeni satır yoksa, printf aramadan sonra neden akmıyor?


539

printfBiçim dizesinde yeni satır yoksa neden çağrıdan sonra durmuyor ? Bu POSIX davranışı mı? printfHer seferinde nasıl hemen sifonu çekebilirim ?


2
bunun herhangi bir dosyada mı yoksa yalnızca terminallerde mi olduğunu araştırdınız mı? ben için geçerli olmaz bekliyoruz gerçi o bir arka plan programından çıkış tamamlanmamış hattına akıllı terminali özelliği olmamak ses olur ön plan programı.
PypeBros

7
Cygwin Bash altında bir satır bile bu aynı kötü davranışı görüyorum olduğu biçim dizesinde. Bu sorun Windows 7 için yenidir; Windows XP'de aynı kaynak kodu sorunsuz çalıştı. MS cmd.exe beklendiği gibi yıkanır. Düzeltme setvbuf(stdout, (char*)NULL, _IONBF, 0)sorun etrafında çalışır, ancak mutlaka gerekli olmamalıdır. MSVC ++ 2008 Express kullanıyorum. ~~~
Steve Pitchers

9
Sorunun başlığını açıklığa kavuşturmak için: printf(..) herhangi bir yıkama yapmazsa , stdoutbir satırsonu (satır arabelleğe alınmışsa) görürken kızarmanın arabelleğe alınmasıdır. Aynı şekilde tepki verecekti putchar('\n');, bu yüzden printf(..)bu konuda özel değil. Bu durumun tersidir cout << endl;, hangi belgelere belirgin kızarıklık bahseder. Printf dokümantasyonu hiç kızarma söz etmez.
Evgeni Sergeev

1
yazma (/ yıkama) potansiyel olarak pahalı bir işlemdir, muhtemelen performans nedenlerinden dolayı arabelleğe alınır.
hanshenrik

@EvgeniSergeev: Sorunun sorunu yanlış teşhis ettiği ve çıktıda yeni satır olduğunda kızarma olduğu konusunda fikir birliği var mı ? (biçim dizgisine bir tane koymak, çıktıya bir tane almanın tek yoludur, ancak tek yol değildir).
Ben Voigt

Yanıtlar:


702

stdoutAkım çizgisi bu bir yeni satır ulaştıktan sonra (ya da söyledim ne zaman) yani sadece tampon içinde ne gösterecek, varsayılan olarak tamponlu edilir. Hemen yazdırmak için birkaç seçeneğiniz vardır:

stderrBunun yerine kullanarak yazdır fprintf( varsayılanstderr olarak arabelleksizdir ):

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

Stdout'u kullanmanız gerektiğinde yıkayın fflush:

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

Düzenleme : Andy Ross'un aşağıdaki yorumundan, aşağıdakileri kullanarak stdout'ta arabelleğe almayı devre dışı bırakabilirsiniz setbuf:

setbuf(stdout, NULL);

veya buradasetvbuf açıklandığı gibi güvenli sürümü

setvbuf(stdout, NULL, _IONBF, 0); 

266
Veya arabelleğe almayı tamamen devre dışı bırakmak için:setbuf(stdout, NULL);
Andy Ross

80
Ayrıca, görünüşe göre UNIX'te, yeni satırın genellikle yalnızca stdout bir terminal ise tamponu temizleyeceğini belirtmek istedim. Çıktı bir dosyaya yönlendiriliyorsa, yeni satır temizlenmez.
hora

5
Ben eklemek gerekir hissediyorum: Ben sadece bu teoriyi test ettik ve ben kullanarak o bulma am setlinebuf()bir terminale yönlendirilmiş olmayan bir dere üzerinde olan her satırın sonunda ateş basması.
Doddy

8
"Başlangıçta açıldığı gibi, standart hata akışı tam olarak arabelleğe alınmaz; standart girdi ve standart çıktı akışları, yalnızca eğer akım etkileşimli bir cihaza atıfta bulunmayacak şekilde belirlenebiliyorsa tamamen arabelleğe alınır" - bu soruya bakın: stackoverflow.com / sorular / 5229096 /…
Seppo Enarvi

3
@RuddZwolinski "Neden yazdırmıyor" sorusunun iyi bir kanon yanıtı olacaksa, " printf bir satırsonu ile karşılaştığında her zaman arabelleği temizliyor mu?" doğrudan bu son derece yükseltilmiş cevap, vs yorumları okumak zorunda insanlar ...
HostileFork SE dont güven diyor

128

Hayır, 's ISO davranış POSIX davranışı değil (tamam, bu ise ancak sadece ölçüde onlar ISO uygun olarak POSIX davranışı).

Etkileşimli bir cihaza atıfta bulunulduğu tespit edilebiliyorsa, standart çıktı satır tamponludur, aksi takdirde tamamen tamponludur. Yani printf, gönderilecek yeni bir satır alsa bile, akmayacak durumlar vardır , örneğin:

myprog >myfile.txt

Bu, verimlilik için anlamlıdır, çünkü bir kullanıcıyla etkileşime giriyorsanız muhtemelen her satırı görmek isterler. Çıktıyı bir dosyaya gönderiyorsanız, büyük olasılıkla diğer ucunda bir kullanıcı yoktur (imkansız olmasa da, dosyayı işliyor olabilirler). Şimdi verebilir kullanıcı her karakteri görmek istediğini ancak iki sorunlar olduğunu savunuyorlar.

Birincisi, çok verimli olmaması. İkincisi, orijinal ANSI C görevinin yeni davranışı icat etmek yerine öncelikle mevcut davranışı kodlamak olduğu ve bu tasarım kararlarının ANSI süreci başlatmadan çok önce alındığıydı. Günümüzde ISO bile standartlarda mevcut kuralları değiştirirken çok dikkatli davranmaktadır.

Bununla nasıl başa çıkılacağı konusunda, fflush (stdout)hemen görmek istediğiniz her çıkış çağrısından sonra, sorunu çözecektir.

Alternatif olarak, setvbufçalıştırmadan önce stdout, arabelleksiz olarak ayarlamak için kullanabilirsiniz ve tüm bu fflushsatırları kodunuza eklemek konusunda endişelenmenize gerek kalmaz :

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Sadece eğer Performansın biraz etkileyebilir unutmayın olan bir dosyaya çıktı gönderme. Ayrıca, bu desteğin standart tarafından garanti edilmeyen uygulama tanımlı olduğunu unutmayın.

ISO C99 bölümü 7.19.3/3ilgili bittir:

Bir akış arabelleğe alınmadığında , karakterlerin kaynaktan veya hedeften en kısa zamanda görünmesi amaçlanır. Aksi takdirde karakterler birikebilir ve ana bilgisayar ortamına / ortamından bir blok olarak iletilebilir.

Bir akış tamamen arabelleğe alındığında , karakterlerin bir arabellek doldurulduğunda bir ana bilgisayar ortamına veya bloktan iletilmesi amaçlanır.

Bir akış satır arabelleğe alındığında , karakterlerin yeni satır karakteri ile karşılaşıldığında ana bilgisayar ortamına veya ana ortamdan bir blok olarak iletilmesi amaçlanır.

Ayrıca, karakterlerin bir arabellek dolduğunda, arabelleksiz bir akışta giriş istendiğinde veya ana bilgisayar ortamından karakterlerin iletilmesini gerektiren bir satır arabellek akışında giriş istendiğinde ana bilgisayar ortamına bir blok olarak iletilmesi amaçlanır. .

Bu özellikler için destek uygulama tanımlıdır ve setbufve setvbufişlevlerinden etkilenebilir .


8
Az önce bir '\ n' olsa bile printf () 'in akmadığı bir senaryo ile karşılaştım. Burada belirttiğiniz gibi bir fflush (stdout) ekleyerek aşıldı. Ancak '\ n' nin printf () içindeki arabelleği temizlememesinin nedenini merak ediyorum.
Qiang Xu

11
@ QiangXu, standart çıktı sadece etkileşimli bir cihaza atıfta bulunulması kesin olarak belirlenebilir durumda satır tamponludur. Dolayısıyla, örneğin, çıktıyı yeniden yönlendirirseniz myprog >/tmp/tmpfile, satır arabelleğe alınmış yerine tamamen arabelleğe alınır. Bellekten, standart çıktınızın etkileşimli olup olmadığının belirlenmesi uygulamaya bırakılır.
paxdiablo

3
ayrıca setvbuf (...., _IOLBF) çağrısı yapan Windows'ta _IOLBF'nin _IOFBF ile aynı olduğu için çalışmaz: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz

28

Muhtemelen verimlilikten dolayı böyle bir şeydir ve tek bir TTY'ye yazma yapan birden fazla programınız varsa, bu şekilde bir satırdaki karakterleri geçmeli olarak almazsınız. Dolayısıyla, A ve B programı çıktı veriyorsa, genellikle:

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

Bu kokuyor, ama daha iyi

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

Bir satırsonuna basmanın garanti edilmediğini unutmayın, bu nedenle yıkama sizin için önemliyse açıkça yıkamalısınız.


26

Çağrıyı hemen yıkamak fflush(stdout)veya fflush(NULL)( NULLher şeyi yıkamak anlamına gelir).


31
Unutmayın fflush(NULL);, genellikle çok kötü bir fikirdir. Birçok dosya açıksa, özellikle kilitler için her şeyle savaşacağınız çok iş parçacıklı bir ortamda performansı öldürür.
R .. GitHub BUZA YARDIMCI DURDUR

14

Not: Microsoft çalışma zamanı kitaplıkları satır arabelleğe almayı desteklemez, bu nedenle printf("will print immediately to terminal"):

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


3
Daha da kötüsü printf, "normal" durumda terminale hemen gidiyor olmasıdır printfve fprintfdaha kaba onların çıkış hemen kullanıma sokulacaktır durumlarda bile tamponlu olsun. MS sabit şeylere sahip olmadıkça, bir programın stderr ve stdout'u diğerinden yakalamasını ve her birine hangi sırayla gönderileceğini tanımlamasını imkansız hale getirir.
supercat

Hayır, arabellek ayarlanmadığı sürece bunu hemen terminale yazdırmaz. Varsayılan olarak tam arabelleğe alma kullanılır
phuclv

12

stdout arabelleğe alınır, bu nedenle yalnızca yeni satır yazdırıldıktan sonra çıktı verilir.

Hemen çıktı almak için:

  1. Stderr'e yazdır.
  2. Stdout'u arabelleksiz yapın.

10
Veya fflush(stdout).
RastaJedi

2
"bu nedenle yalnızca yeni satır yazdırıldıktan sonra çıktı verilir." Sadece bu değil, en az 4 vaka daha. tampon dolu, yaz stderr(bu cevap daha sonra bahsedilecektir) fflush(stdout),, fflush(NULL).
chux - Monica'yı

11

varsayılan olarak, stdout satır arabelleğe alınır, stderr hiçbiri arabelleğe alınmaz ve dosya tamamen arabelleğe alınır.


10

Bunun yerine tamponlanmamış stderr'e fprintf ekleyebilirsiniz. Veya stdout'u istediğiniz zaman yıkayabilirsiniz. Veya stdout'u arabelleksiz olarak ayarlayabilirsiniz.



2

Genellikle 2 tamponlama seviyesi vardır.

1. Çekirdek tampon Önbelleği (okuma / yazma işlemini hızlandırır)

2. G / Ç kütüphanesinde tamponlama (sistem çağrısı sayısını azaltır)

Örnek verelim fprintf and write().

Aradığınızda fprintf(), doğrudan dosyaya wirte olmaz. Önce programın belleğindeki stdio arabelleğine gider. Oradan yazma sistem çağrısı kullanılarak çekirdek tampon önbelleğine yazılır. Bu nedenle, G / Ç arabelleğini atlamanın bir yolu doğrudan write () kullanmaktır. Diğer yollar kullanmaktır setbuff(stream,NULL). Bu, arabelleğe alma modunu arabelleğe alma olmadan ayarlar ve veriler doğrudan çekirdek arabelleğine yazılır. Verilerin çekirdek arabelleğine zorla kaydırılmasını sağlamak için, 'satır arabelleğe alma' varsayılan arabelleğe alma modu durumunda G / Ç arabelleğini temizleyecek olan "\ n" kullanabiliriz. Veya kullanabiliriz fflush(FILE *stream).

Şimdi çekirdek tamponundayız. Çekirdek (/ OS) disk erişim süresini en aza indirmek istiyor ve bu nedenle sadece disk bloklarını okuyor / yazıyor. Yani a read()verildiğinde, bu bir sistem çağrısıdır ve doğrudan veya üzerinden çağrılabilirfscanf() çekirdek disk bloğunu diskten okur ve bir arabellekte saklar. Bundan sonra veriler buradan kullanıcı alanına kopyalanır.

Benzer şekilde, fprintf()G / Ç arabelleğinden alınan veriler çekirdek tarafından diske yazılır. Bu, read () write () işlevini daha hızlı hale getirir.

Şimdi çekirdeği bir başlatmaya zorlamak için write(), bundan sonra veri aktarımı donanım kontrolörleri tarafından kontrol edilir, ayrıca bazı yollar vardır. Biz kullanabilir O_SYNCveya benzer bayrakları yazma aramalar sırasında. Ya fsync(),fdatasync(),sync()da çekirdek tamponunda veri bulunur bulunmaz çekirdeğin yazma işlemini başlatması gibi diğer işlevleri de kullanabiliriz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.