เหตุใด printf จึงไม่ลบออกหลังจากการโทรยกเว้นว่ามีการขึ้นบรรทัดใหม่ในสตริงรูปแบบ


539

ทำไมprintfไม่ล้างออกหลังจากการโทรยกเว้นว่ามีการขึ้นบรรทัดใหม่ในสตริงรูปแบบ พฤติกรรม POSIX นี้หรือไม่ ฉันจะprintfล้างทันทีทุกครั้งได้อย่างไร


2
คุณได้ตรวจสอบว่าสิ่งนี้เกิดขึ้นกับไฟล์ใด ๆ หรือเฉพาะกับเทอร์มินัล ว่าจะเสียงจะเป็นคุณลักษณะขั้วฉลาดที่จะไม่ส่งออกสายที่ยังไม่เสร็จจากโปรแกรมพื้นหลัง แต่ผมคาดหวังว่ามันจะไม่นำไปใช้กับโปรแกรมเบื้องหน้า
PypeBros

7
ภายใต้การทุบตี Cygwin ฉันเห็นความประพฤติไม่ดีเช่นนี้แม้ว่าบรรทัดใหม่จะอยู่ในรูปแบบของสตริง ปัญหานี้เป็นเรื่องใหม่สำหรับ Windows 7 ซอร์สโค้ดเดียวกันทำงานได้ดีบน Windows XP MS cmd.exe วูบวาบตามที่คาดไว้ การแก้ไขสามารถแก้ไขsetvbuf(stdout, (char*)NULL, _IONBF, 0)ปัญหาได้ แต่แน่นอนไม่จำเป็น ฉันใช้ MSVC ++ 2008 Express ~~~
Steve Pitchers

9
หากต้องการชี้แจงชื่อคำถามให้ชัดเจน: printf(..) ไม่ได้ทำการฟลัชใด ๆ เลยมันเป็นการบัฟเฟอร์stdoutที่อาจจะฟลัชเมื่อเห็นบรรทัดใหม่ (ถ้าเป็นบรรทัดที่บัฟเฟอร์) มันจะตอบสนองแบบเดียวกับputchar('\n');ดังนั้นจึงprintf(..)ไม่พิเศษในเรื่องนี้ นี้เป็นในทางตรงกันข้ามกับcout << endl;ที่เอกสารที่เด่นชัดกล่าวถึงการล้าง เอกสาร printfไม่ได้พูดถึงการล้างที่ทั้งหมด
Evgeni Sergeev

1
การเขียน (/ การล้าง) อาจเป็นการดำเนินการที่มีราคาแพงซึ่งอาจถูกบัฟเฟอร์เนื่องจากเหตุผลด้านประสิทธิภาพ
hanshenrik

@EvgeniSergeev: มีฉันทามติที่คำถามมีการวินิจฉัยปัญหาที่ไม่ถูกต้องและการล้างเกิดขึ้นเมื่อมีการขึ้นบรรทัดใหม่ในผลลัพธ์หรือไม่ (การใส่หนึ่งในสตริงรูปแบบเป็นวิธีหนึ่ง แต่ไม่ใช่วิธีเดียวในการรับหนึ่งในเอาต์พุต)
Ben Voigt

คำตอบ:


702

stdoutกระแสมีบัฟเฟอร์บรรทัดโดยค่าเริ่มต้นเท่านั้นดังนั้นจะแสดงสิ่งที่อยู่ในบัฟเฟอร์หลังจากที่มันถึงบรรทัดใหม่ (หรือเมื่อมันบอกให้) คุณมีตัวเลือกที่จะพิมพ์ได้ทันที:

พิมพ์เพื่อstderrใช้แทนfprintf( stderrไม่ได้รับการแก้ไขตามค่าเริ่มต้น ):

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

ล้าง stdout ทุกครั้งที่คุณต้องการใช้fflush:

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

แก้ไข : จากความคิดเห็นของ Andy Ross ด้านล่างคุณสามารถปิดใช้งานการบัฟเฟอร์เมื่อ stdout โดยใช้setbuf:

setbuf(stdout, NULL);

หรือรุ่นที่ปลอดภัยsetvbufตามที่อธิบายไว้ที่นี่

setvbuf(stdout, NULL, _IONBF, 0); 

266
หรือปิดการใช้งานบัฟเฟอร์ทั้งหมด:setbuf(stdout, NULL);
แอนดี้รอสส์

80
นอกจากนี้ยังต้องการที่จะพูดถึงที่เห็นได้ชัดว่าใน UNIX บรรทัดใหม่โดยทั่วไปจะล้างเพียงบัฟเฟอร์ถ้า stdout เป็นขั้ว หากการส่งออกกำลังถูกเปลี่ยนเส้นทางไปยังไฟล์บรรทัดใหม่จะไม่ล้าง
ฟลอรา

5
ฉันรู้สึกว่าฉันควรเพิ่ม: ฉันเพิ่งทดสอบทฤษฎีนี้และฉันพบว่าการใช้งานsetlinebuf()บนสตรีมที่ไม่ได้พุ่งไปที่เทอร์มินัลจะล้างข้อมูลในตอนท้ายของแต่ละบรรทัด
Doddy

8
"เมื่อเปิดครั้งแรกสตรีมข้อผิดพลาดมาตรฐานจะไม่ถูกบัฟเฟอร์เต็มสตรีมอินพุตมาตรฐานและสตรีมเอาต์พุตมาตรฐานจะถูกบัฟเฟอร์อย่างสมบูรณ์หากว่าสามารถกำหนดกระแสไม่ให้อ้างถึงอุปกรณ์แบบโต้ตอบได้" - ดูคำถามนี้: stackoverflow.com / คำถาม / 5229096 / …
Seppo Enarvi

3
@RuddZwolinski ถ้าเป็นไปได้คำตอบที่ดีของ "ทำไมมันไม่พิมพ์" มันเป็นสิ่งสำคัญที่จะพูดถึงความแตกต่าง terminal / ไฟล์ตาม"printf จะล้างบัฟเฟอร์เมื่อพบบรรทัดใหม่หรือไม่?" โดยตรงในคำตอบ upvoted นี้สูงคน vs ต้องอ่านความคิดเห็น ...
HostileFork พูดว่าไม่ไว้วางใจ SE

128

ไม่มันไม่ใช่พฤติกรรมของ POSIX แต่เป็นพฤติกรรมของ ISO (แต่ก็เป็นพฤติกรรมของ POSIX แต่จะทำได้เพียงตราบเท่าที่สอดคล้องกับ ISO)

เอาต์พุตมาตรฐานเป็นบัฟเฟอร์บรรทัดหากตรวจพบการอ้างถึงอุปกรณ์แบบอินเทอร์มินัลมิฉะนั้นจะถูกบัฟเฟอร์เต็ม ดังนั้นจึงมีสถานการณ์ที่printfไม่ล้างข้อมูลแม้ว่าจะได้รับบรรทัดใหม่ในการส่งออกเช่น:

myprog >myfile.txt

สิ่งนี้เหมาะสมสำหรับประสิทธิภาพเนื่องจากถ้าคุณโต้ตอบกับผู้ใช้พวกเขาอาจต้องการเห็นทุกบรรทัด หากคุณกำลังส่งเอาต์พุตไปยังไฟล์เป็นไปได้มากว่าไม่มีผู้ใช้ที่ปลายอีกด้านหนึ่ง (แม้ว่าจะเป็นไปไม่ได้ก็ตาม ตอนนี้คุณสามารถยืนยันได้ว่าผู้ใช้ต้องการเห็นตัวละครทุกตัว แต่มีปัญหาสองประการ

อย่างแรกคือมันไม่ได้มีประสิทธิภาพมาก อย่างที่สองก็คือเอกสารต้นฉบับของ ANSI C คือการประมวลผลพฤติกรรมที่มีอยู่เป็นหลักมากกว่าที่จะคิดค้นพฤติกรรมใหม่และการตัดสินใจออกแบบเหล่านั้นได้ทำมานานก่อนที่ ANSI จะเริ่มกระบวนการ แม้แต่ทุกวันนี้ ISO ก็ยังดำเนินการอย่างระมัดระวังเมื่อเปลี่ยนกฎที่มีอยู่ในมาตรฐาน

สำหรับวิธีจัดการกับสิ่งนั้นหากคุณfflush (stdout)หลังจากทุกการเรียกเอาต์พุตที่คุณต้องการดูทันทีจะช่วยแก้ปัญหาได้

หรือคุณสามารถใช้setvbufก่อนที่จะเปิดใช้งานstdoutเพื่อตั้งเป็น unbuffered และคุณไม่ต้องกังวลเกี่ยวกับการเพิ่มfflushบรรทัดเหล่านั้นทั้งหมดในรหัสของคุณ:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

เพียงจำไว้ว่าอาจส่งผลกระทบต่อประสิทธิภาพการทำงานเล็กน้อยหากคุณกำลังส่งออกไปยังไฟล์ โปรดทราบว่าการสนับสนุนสำหรับสิ่งนี้คือการใช้งานที่กำหนดไม่ได้รับการรับรองตามมาตรฐาน

ส่วน ISO C99 7.19.3/3เป็นบิตที่เกี่ยวข้อง:

เมื่อกระแสเป็นunbufferedตัวละครมีความตั้งใจที่จะปรากฏจากแหล่งที่มาหรือที่ปลายทางเร็วที่สุดเท่าที่เป็นไปได้ มิฉะนั้นตัวละครอาจถูกสะสมและส่งไปยังหรือจากสภาพแวดล้อมโฮสต์เป็นบล็อก

เมื่อสตรีมถูกบัฟเฟอร์อย่างสมบูรณ์อักขระจะถูกส่งไปยังหรือจากสภาพแวดล้อมโฮสต์เป็นบล็อกเมื่อบัฟเฟอร์เต็ม

เมื่อสตรีมถูกบัฟเฟอร์บรรทัดตัวอักษรมีวัตถุประสงค์เพื่อส่งไปยังหรือจากสภาพแวดล้อมโฮสต์เป็นบล็อกเมื่อพบอักขระบรรทัดใหม่

นอกจากนี้อักขระมีจุดประสงค์เพื่อส่งเป็นบล็อกไปยังสภาพแวดล้อมโฮสต์เมื่อบัฟเฟอร์เต็มเมื่อมีการร้องขออินพุตในสตรีมที่ไม่บัฟเฟอร์หรือเมื่อมีการร้องขออินพุตในสตรีมบัฟเฟอร์บรรทัดที่ต้องการการส่งอักขระจากสภาพแวดล้อมโฮสต์ .

การสนับสนุนสำหรับคุณสมบัติเหล่านี้คือการกำหนดการใช้งานและอาจได้รับผลกระทบผ่านทางsetbufและsetvbufฟังก์ชั่น


8
ฉันเพิ่งเจอสถานการณ์ที่แม้จะมี '\ n', printf () ไม่ล้าง มันเอาชนะด้วยการเพิ่ม fflush (stdout) ตามที่คุณพูดถึงที่นี่ แต่ฉันสงสัยว่าทำไม '\ n' ล้มเหลวในการล้างบัฟเฟอร์ใน printf ()
Qiang Xu

11
@QiangXu เอาต์พุตมาตรฐานเป็นบัฟเฟอร์บรรทัดเฉพาะในกรณีที่สามารถกำหนดอย่างแน่นอนเพื่ออ้างถึงอุปกรณ์แบบโต้ตอบ ตัวอย่างเช่นหากคุณเปลี่ยนทิศทางเอาต์พุตด้วยmyprog >/tmp/tmpfileนั่นจะถูกบัฟเฟอร์อย่างสมบูรณ์แทนที่จะเป็นบัฟเฟอร์บรรทัด จากหน่วยความจำการพิจารณาว่าเอาต์พุตมาตรฐานของคุณเป็นแบบอินเทอร์แอคทีฟจะถูกนำไปใช้งานหรือไม่
paxdiablo

3
นอกจากนี้การเรียกใช้ Windows setvbuf (.... , _IOLBF) จะไม่ทำงานเมื่อ _IOLBF เหมือนกับ _IOFBF ที่นั่น: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz

28

อาจเป็นเพราะประสิทธิภาพและถ้าคุณมีหลายโปรแกรมที่เขียนไปยัง TTY เดียวด้วยวิธีนี้คุณจะไม่ได้รับตัวอักษรในบรรทัดที่ถูกแทรกเข้าด้วยกัน ดังนั้นหากโปรแกรม A และ B กำลังแสดงผลคุณจะได้รับ:

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

สิ่งนี้มีกลิ่นเหม็น แต่ดีกว่า

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

โปรดทราบว่าไม่รับประกันว่าจะมีการล้างข้อมูลในบรรทัดใหม่ดังนั้นคุณควรล้างข้อมูลให้ชัดเจนหากมีการล้างข้อมูลสำคัญกับคุณ


26

หากต้องการโทรออกทันทีfflush(stdout)หรือfflush(NULL)( NULLหมายถึงล้างข้อมูลทุกอย่าง)


31
โปรดทราบว่าfflush(NULL);โดยทั่วไปแล้วเป็นความคิดที่แย่มาก มันจะฆ่าประสิทธิภาพหากคุณเปิดไฟล์จำนวนมากโดยเฉพาะในสภาพแวดล้อมแบบมัลติเธรดที่คุณจะต่อสู้เพื่อล็อค
. GitHub หยุดช่วยน้ำแข็ง

14

หมายเหตุ: ไลบรารีรันไทม์ Microsoft ไม่รองรับการบัฟเฟอร์บรรทัดดังนั้นprintf("will print immediately to terminal"):

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


3
เลวร้ายยิ่งกว่าการprintfไปที่เทอร์มินัลในกรณี "ปกติ" คือความจริงที่ว่าprintfและfprintfรับบัฟเฟอร์ที่หยาบกว่าแม้ในกรณีที่เอาต์พุตของพวกเขาถูกนำไปใช้งานทันที ถ้า MS ไม่ได้แก้ไขสิ่งต่าง ๆ ทำให้มันเป็นไปไม่ได้ที่โปรแกรมใดโปรแกรมหนึ่งจะจับ stderr และ stdout จากอีกโปรแกรมหนึ่งและระบุว่าสิ่งใดที่ถูกส่งไปตามลำดับ
supercat

ไม่มันไม่ได้พิมพ์ลงในเครื่องทันทีเว้นแต่จะไม่มีการกำหนดบัฟเฟอร์ โดยค่าเริ่มต้นการบัฟเฟอร์เต็ม
phuclv

12

stdout ถูกบัฟเฟอร์ดังนั้นจะแสดงผลหลังจากพิมพ์บรรทัดใหม่เท่านั้น

หากต้องการรับเอาต์พุตทันทีให้ทำดังนี้:

  1. พิมพ์ไปยัง stderr
  2. ทำให้ stdout ไม่มีข้อผิดพลาด

10
fflush(stdout)หรือ
RastaJedi

2
"ดังนั้นจะส่งออกหลังจากพิมพ์บรรทัดใหม่เท่านั้น" ไม่เพียงแค่นี้ แต่อย่างน้อย 4 กรณีอื่น ๆ buffer เต็มรูปแบบการเขียน(คำตอบนี้กล่าวถึงในภายหลัง)stderr , fflush(stdout)fflush(NULL)
chux - Reinstate Monica

11

โดยค่าเริ่มต้น stdout เป็นบรรทัดบัฟเฟอร์ stderr ไม่มีบัฟเฟอร์และไฟล์จะถูกบัฟเฟอร์อย่างสมบูรณ์


10

คุณสามารถ fprintf ถึง stderr ซึ่งไม่มีข้อผิดพลาดแทน หรือคุณสามารถล้างข้อมูล stdout เมื่อคุณต้องการ หรือคุณสามารถตั้งค่า stdout เป็น unbuffered



2

โดยทั่วไปมีการบัฟเฟอร์ 2 ระดับ -

1. เคอร์เนลบัฟเฟอร์แคช (ทำให้การอ่าน / เขียนเร็วขึ้น)

2. การบัฟเฟอร์ในไลบรารี I / O (ลดจำนวนการโทรของระบบ)

นำตัวอย่าง Let 's fprintf and write()ของ

เมื่อคุณโทรfprintf()มันจะไม่ wirte ไปยังไฟล์โดยตรง ก่อนอื่นจะไปที่ stdio buffer ในหน่วยความจำของโปรแกรม จากนั้นจะถูกเขียนไปยังแคชบัฟเฟอร์ของเคอร์เนลโดยใช้การเรียกระบบการเขียน ดังนั้นวิธีหนึ่งในการข้ามบัฟเฟอร์ I / O คือการใช้ write () โดยตรง วิธีการอื่น ๆ setbuff(stream,NULL)โดยใช้ สิ่งนี้ตั้งค่าโหมดการบัฟเฟอร์เป็นไม่มีการบัฟเฟอร์และข้อมูลจะถูกเขียนไปยังเคอร์เนลบัฟเฟอร์โดยตรง ในการทำให้ข้อมูลถูกเปลี่ยนเป็นบัฟเฟอร์ของเคอร์เนลเราสามารถใช้ "\ n" ซึ่งในกรณีของโหมดบัฟเฟอร์เริ่มต้นของ 'line buffering' จะล้างบัฟเฟอร์ I / O fflush(FILE *stream)หรือเราสามารถใช้

ตอนนี้เราอยู่ในเคอร์เนลบัฟเฟอร์ เคอร์เนล (/ OS) ต้องการลดเวลาในการเข้าถึงดิสก์และด้วยเหตุนี้จึงสามารถอ่าน / เขียนเฉพาะบล็อกของดิสก์ ดังนั้นเมื่อมีการread()ออกa ซึ่งเป็นการเรียกระบบและสามารถเรียกใช้โดยตรงหรือผ่านfscanf()เคอร์เนลอ่านบล็อกดิสก์จากดิสก์และเก็บไว้ในบัฟเฟอร์ หลังจากนั้นข้อมูลจะถูกคัดลอกจากที่นี่ไปยังพื้นที่ผู้ใช้

ในทำนองเดียวกันfprintf()ข้อมูลที่ได้รับจากบัฟเฟอร์ I / O จะถูกเขียนไปยังดิสก์โดยเคอร์เนล ทำให้อ่าน () เขียน () เร็วขึ้น

ตอนนี้เพื่อบังคับให้เคอร์เนลเริ่มต้น a write()หลังจากที่การถ่ายโอนข้อมูลถูกควบคุมโดยตัวควบคุมฮาร์ดแวร์ก็มีบางวิธีเช่นกัน เราสามารถใช้O_SYNCหรือคล้ายกันธงระหว่างเขียนสาย หรือเราสามารถใช้ฟังก์ชั่นอื่น ๆ เช่นfsync(),fdatasync(),sync()ทำให้เคอร์เนลเริ่มต้นการเขียนทันทีที่มีข้อมูลในเคอร์เนลบัฟเฟอร์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.