Tại sao printf không tuôn ra sau cuộc gọi trừ khi một dòng mới có trong chuỗi định dạng?


539

Tại sao printfkhông tuôn ra sau cuộc gọi trừ khi một dòng mới trong chuỗi định dạng? Đây có phải là hành vi POSIX không? Làm thế nào tôi có thể có printfngay lập tức tuôn ra mỗi lần?


2
Bạn đã điều tra liệu điều này xảy ra với bất kỳ tập tin hoặc chỉ với thiết bị đầu cuối? đó sẽ âm thanh trở thành một tính năng thiết bị đầu cuối thông minh không ra đường chưa hoàn thành từ một chương trình nền, mặc dù tôi hy vọng nó sẽ không áp dụng cho các chương trình foreground.
PypeBros

7
Theo Cygwin bash, tôi thấy điều tương tự như vậy ngay cả khi một dòng mới trong chuỗi định dạng. Vấn đề này là mới đối với Windows 7; mã nguồn tương tự hoạt động tốt trên Windows XP. MS cmd.exe tuôn ra như mong đợi. Việc khắc phục có setvbuf(stdout, (char*)NULL, _IONBF, 0)vấn đề xung quanh vấn đề, nhưng chắc chắn không cần thiết. Tôi đang sử dụng MSVC ++ 2008 Express. ~~~
Steve Pitchers

9
Để làm rõ tiêu đề của câu hỏi: printf(..) không tự thực hiện bất kỳ thao tác nào , đó là bộ đệm của stdoutcâu hỏi có thể bị xóa khi nhìn thấy một dòng mới (nếu nó được đệm dòng). Nó sẽ phản ứng theo cách tương tự putchar('\n');, vì vậy printf(..)không có gì đặc biệt trong vấn đề này. Điều này trái ngược với cout << endl;, tài liệu trong đó nổi bật đề cập đến việc xả nước. Các tài liệu của printf hoàn toàn không đề cập đến việc xả nước.
Evgeni Sergeev

1
viết (/ xả) có khả năng là một hoạt động đắt tiền, nó có thể được đệm vì lý do hiệu suất.
hanshenrik

@EvgeniSergeev: Có sự đồng thuận rằng câu hỏi đã chẩn đoán không chính xác vấn đề và việc xả nước đó xảy ra khi có một dòng mới trong đầu ra ? (đặt một trong chuỗi định dạng là một cách, nhưng không phải là cách duy nhất, để có được một trong đầu ra).
Ben Voigt

Câu trả lời:


702

Các stdoutdòng được dòng đệm theo mặc định, vì vậy sẽ chỉ hiển thị những gì trong bộ đệm sau khi nó đạt đến một dòng mới (hoặc khi nó được nói đến). Bạn có một vài tùy chọn để in ngay lập tức:

stderrThay vào đó, in bằng cách sử dụng fprintf(không stderrcó bộ đệm theo mặc định ):

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

Flush stdout bất cứ khi nào bạn cần nó để sử dụng fflush:

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

Chỉnh sửa : Từ nhận xét của Andy Ross bên dưới, bạn cũng có thể tắt bộ đệm trên thiết bị xuất chuẩn bằng cách sử dụng setbuf:

setbuf(stdout, NULL);

hoặc phiên bản bảo mật của nó setvbufnhư được giải thích ở đây

setvbuf(stdout, NULL, _IONBF, 0); 

266
Hoặc, để tắt hoàn toàn bộ đệm:setbuf(stdout, NULL);
Andy Ross

80
Ngoài ra, chỉ muốn đề cập rằng rõ ràng trong UNIX, một dòng mới thường sẽ chỉ xóa bộ đệm nếu thiết bị xuất chuẩn là thiết bị đầu cuối. Nếu đầu ra đang được chuyển hướng đến một tập tin, một dòng mới sẽ không tuôn ra.
hora

5
Tôi cảm thấy rằng tôi nên thêm: Tôi vừa mới thử nghiệm lý thuyết này và tôi thấy rằng việc sử dụng setlinebuf()trên một luồng không được hướng đến một thiết bị đầu cuối đang tuôn ra ở cuối mỗi dòng.
Doddy

8
"Khi được mở ban đầu, luồng lỗi tiêu chuẩn không được đệm hoàn toàn; luồng đầu vào tiêu chuẩn và đầu ra tiêu chuẩn được đệm hoàn toàn khi và chỉ khi luồng có thể được xác định không tham chiếu đến thiết bị tương tác" - xem câu hỏi này: stackoverflow.com / câu hỏi / 5229096 / Hoài
Seppo Enarvi

3
@RuddZwolinski Nếu đây sẽ là một câu trả lời chính xác về "tại sao nó không in" thì có vẻ quan trọng khi đề cập đến phân biệt thiết bị đầu cuối / tệp theo "Printf có luôn xóa bộ đệm khi gặp dòng mới không?" trực tiếp trong câu trả lời được đánh giá cao này, so với những người cần đọc các bình luận ...
HostileFork nói không tin tưởng vào

128

Không, đó không phải là hành vi POSIX, đó là hành vi ISO (tốt, đó hành vi POSIX nhưng chỉ trong chừng mực khi chúng tuân thủ ISO).

Đầu ra tiêu chuẩn được đệm dòng nếu có thể được phát hiện để chỉ một thiết bị tương tác, nếu không, nó được đệm hoàn toàn. Vì vậy, có những tình huống printfsẽ không tuôn ra, ngay cả khi nó nhận được một dòng mới để gửi đi, chẳng hạn như:

myprog >myfile.txt

Điều này có ý nghĩa về hiệu quả vì nếu bạn tương tác với người dùng, họ có thể muốn xem mọi dòng. Nếu bạn đang gửi đầu ra tới một tệp, rất có thể không có người dùng ở đầu kia (mặc dù không phải là không thể, họ có thể theo đuôi tệp). Bây giờ bạn có thể lập luận rằng người dùng muốn nhìn thấy mọi nhân vật nhưng có hai vấn đề với điều đó.

Đầu tiên là nó không hiệu quả lắm. Thứ hai là nhiệm vụ ANSI C ban đầu chủ yếu là mã hóa hành vi hiện có , thay vì phát minh ra hành vi mới và những quyết định thiết kế đó đã được đưa ra từ lâu trước khi ANSI bắt đầu quá trình. Ngay cả ISO ngày nay cũng rất cẩn thận khi thay đổi các quy tắc hiện có trong các tiêu chuẩn.

Về cách giải quyết vấn đề đó, nếu bạn fflush (stdout)sau mỗi cuộc gọi đầu ra mà bạn muốn xem ngay lập tức, điều đó sẽ giải quyết vấn đề.

Ngoài ra, bạn có thể sử dụng setvbuftrước khi vận hành stdout, để đặt nó thành không có bộ đệm và bạn sẽ không phải lo lắng về việc thêm tất cả các fflushdòng đó vào mã của mình:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Chỉ cần lưu ý rằng có thể ảnh hưởng đến hiệu suất khá nhiều nếu bạn đang gửi đầu ra đến một tệp. Ngoài ra, hãy nhớ rằng hỗ trợ cho việc này được xác định theo triển khai, không được đảm bảo bởi tiêu chuẩn.

Phần ISO C99 7.19.3/3là bit có liên quan:

Khi một luồng không có bộ đệm , các ký tự được dự định xuất hiện từ nguồn hoặc tại đích càng sớm càng tốt. Mặt khác, các ký tự có thể được tích lũy và truyền đến hoặc từ môi trường máy chủ dưới dạng một khối.

Khi một luồng được đệm hoàn toàn , các ký tự được dự định được truyền đến hoặc từ môi trường máy chủ dưới dạng một khối khi bộ đệm được lấp đầy.

Khi một luồng được đệm dòng , các ký tự được dự định được truyền đến hoặc từ môi trường máy chủ dưới dạng một khối khi gặp phải một ký tự dòng mới.

Hơn nữa, các ký tự được dự định được truyền dưới dạng một khối đến môi trường máy chủ khi bộ đệm được lấp đầy, khi đầu vào được yêu cầu trên luồng không có bộ đệm hoặc khi đầu vào được yêu cầu trên luồng được đệm yêu cầu truyền các ký tự từ môi trường máy chủ .

Hỗ trợ cho các đặc điểm này được xác định theo triển khai và có thể bị ảnh hưởng thông qua các chức năng setbufsetvbuf.


8
Tôi vừa bắt gặp một kịch bản mà thậm chí còn có một '\ n', printf () không tuôn ra. Nó đã được khắc phục bằng cách thêm một fflush (stdout), như bạn đã đề cập ở đây. Nhưng tôi tự hỏi lý do tại sao '\ n' không thể xóa bộ đệm trong printf ().
Qiang Xu

11
@QiangXu, đầu ra tiêu chuẩn chỉ được đệm dòng trong trường hợp có thể xác định dứt khoát để tham chiếu đến một thiết bị tương tác. Vì vậy, ví dụ, nếu bạn chuyển hướng đầu ra với myprog >/tmp/tmpfile, nó được đệm hoàn toàn thay vì đệm dòng. Từ bộ nhớ, việc xác định xem đầu ra tiêu chuẩn của bạn có tương tác hay không được dành cho việc thực hiện.
paxdiablo

3
hơn nữa trên Windows, gọi setvbuf (...., _IOLBF) sẽ không hoạt động vì _IOLBF giống như _IOFBF ở đó: msdn.microsoft.com/en-us/l
Library / 86cebhfs.aspx

28

Có thể là như vậy vì tính hiệu quả và bởi vì nếu bạn có nhiều chương trình viết vào một TTY, theo cách này bạn sẽ không nhận được các ký tự trên một dòng xen kẽ. Vì vậy, nếu chương trình A và B xuất ra, bạn thường sẽ nhận được:

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

Cái này hôi thối, nhưng nó tốt hơn

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

Lưu ý rằng nó thậm chí không được đảm bảo để tuôn ra một dòng mới, vì vậy bạn nên xóa một cách rõ ràng nếu việc xả nước có vấn đề với bạn.


26

Để ngay lập tức gọi cuộc gọi fflush(stdout)hoặc fflush(NULL)( NULLcó nghĩa là tuôn ra mọi thứ).


31
Hãy ghi nhớ fflush(NULL);thường là một ý tưởng rất xấu. Nó sẽ giết hiệu suất nếu bạn mở nhiều tệp, đặc biệt là trong môi trường đa luồng, nơi bạn sẽ chiến đấu với mọi thứ để khóa.
R .. GitHub DỪNG GIÚP ICE

14

Lưu ý: Thư viện thời gian chạy của Microsoft không hỗ trợ bộ đệm dòng, vì vậy printf("will print immediately to terminal"):

https://docs.microsoft.com/en-us/cpp/c-r nb-l Library / reference / setvbuf


3
Tệ hơn là printfđi ngay đến thiết bị đầu cuối trong trường hợp "bình thường" là thực tế printffprintfđược đệm thô hơn ngay cả trong trường hợp đầu ra của chúng được đưa vào sử dụng ngay lập tức. Trừ khi MS có những thứ cố định, điều đó khiến cho một chương trình không thể nắm bắt được thiết bị xuất chuẩn và thiết bị xuất chuẩn từ một thiết bị khác và xác định thứ tự nào được gửi đến từng thứ.
supercat

không, nó không in ngay lập tức đến thiết bị đầu cuối trừ khi không có bộ đệm. Theo mặc định, bộ đệm đầy đủ được sử dụng
phuclv

12

thiết bị xuất chuẩn được đệm, do đó sẽ chỉ xuất sau khi một dòng mới được in.

Để có được đầu ra ngay lập tức, một trong hai:

  1. In ra stderr.
  2. Làm cho stdout không có bộ đệm.

10
Hoặc fflush(stdout).
RastaJedi

2
"vì vậy sẽ chỉ xuất sau khi một dòng mới được in." Không chỉ điều này mà ít nhất 4 trường hợp khác. đệm đầy đủ, ghi vào stderr(câu trả lời này đề cập sau) fflush(stdout), fflush(NULL).
chux - Phục hồi Monica

11

theo mặc định, thiết bị xuất chuẩn được đệm dòng, thiết bị xuất chuẩn không được đệm và tệp hoàn toàn được đệm.


10

Thay vào đó, bạn có thể fprintf đến stderr, không có bộ đệm. Hoặc bạn có thể xả stdout khi bạn muốn. Hoặc bạn có thể đặt thiết bị xuất chuẩn thành không có bộ đệm.



2

Nhìn chung có 2 cấp độ đệm-

1. Bộ đệm hạt nhân Cache (giúp đọc / ghi nhanh hơn)

2. Bộ đệm trong thư viện I / O (giảm số lần gọi hệ thống)

Hãy lấy ví dụ về fprintf and write().

Khi bạn gọi fprintf(), nó sẽ không trực tiếp vào tập tin. Đầu tiên, nó đi đến bộ đệm stdio trong bộ nhớ của chương trình. Từ đó, nó được ghi vào bộ đệm bộ đệm kernel bằng cách sử dụng lệnh gọi hệ thống ghi. Vì vậy, một cách để bỏ qua bộ đệm I / O là trực tiếp sử dụng write (). Những cách khác là bằng cách sử dụng setbuff(stream,NULL). Cái này đặt chế độ đệm thành không có bộ đệm và dữ liệu được ghi trực tiếp vào bộ đệm kernel. Để mạnh mẽ làm cho dữ liệu được chuyển sang bộ đệm kernel, chúng ta có thể sử dụng "\ n", trong trường hợp chế độ bộ đệm mặc định của 'bộ đệm dòng', sẽ xóa bộ đệm I / O. Hoặc chúng ta có thể sử dụng fflush(FILE *stream).

Bây giờ chúng ta đang ở trong bộ đệm kernel. Kernel (/ OS) muốn giảm thiểu thời gian truy cập đĩa và do đó nó chỉ đọc / ghi các khối đĩa. Vì vậy, khi a read()được phát hành, đó là một cuộc gọi hệ thống và có thể được gọi trực tiếp hoặc thông qua fscanf(), kernel đọc khối đĩa từ đĩa và lưu trữ nó trong một bộ đệm. Sau đó dữ liệu được sao chép từ đây vào không gian người dùng.

Tương tự, fprintf()dữ liệu nhận được từ bộ đệm I / O được ghi vào đĩa bởi kernel. Điều này làm cho read () write () nhanh hơn.

Bây giờ để buộc kernel khởi tạo a write(), sau đó việc truyền dữ liệu được điều khiển bởi các bộ điều khiển phần cứng, cũng có một số cách. Chúng ta có thể sử dụng O_SYNChoặc các cờ tương tự trong khi viết cuộc gọi. Hoặc chúng ta có thể sử dụng các hàm khác như fsync(),fdatasync(),sync()để làm cho kernel khởi tạo ghi ngay khi dữ liệu có sẵn trong bộ đệm kernel.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.