2012-03-11 44 views
29

Tôi vừa biết về sự tồn tại của hàm ios_base::sync_with_stdio, về cơ bản cho phép bạn tắt (hoặc bật nếu bạn đã tắt) đồng bộ hóa giữa các luồng iostream được sử dụng trong C++ và các luồng cstdio.luồng cstdio và luồng iostream?

Bây giờ, tôi luôn nghĩ rằng stdout, stderrstdin trong C cơ bản được gói trong một tập hợp các đối tượng trong C++ trong các lớp iostream. Nhưng nếu họ phải đồng bộ với nhau, điều này sẽ chỉ ra rằng iostream lớp C++ 's là không một wrapper xung quanh C stdin, vv

tôi khá bối rối bởi điều này? Ai đó có thể làm rõ làm thế nào iSOream của C + + và stdio của C là khác nhau những điều mà làm chính xác điều tương tự, chỉ ở một mức độ trừu tượng khác nhau? Tôi nghĩ rằng họ là điều tương tự!?

Làm thế nào để chúng được đồng bộ hóa? Tôi luôn nghĩ rằng chúng giống nhau, về cơ bản là một gói khác.

+4

+1, woah Tôi luôn nghĩ rằng C++ 'iostream' là một trình bao bọc của C' stdio'. – ApprenticeHacker

Trả lời

33

Tiêu chuẩn C và C++ không có yêu cầu về cách mọi thứ được triển khai, chỉ về tác động của một số hoạt động nhất định. Đối với các chức năng <stdio> so với <iostream> điều này có nghĩa là người ta có thể bọc cái kia, cả hai có thể cơ bản giống nhau, hoặc chúng hoàn toàn độc lập. Về mặt kỹ thuật, việc sử dụng một cài đặt phổ biến sẽ là lý tưởng vì nhiều lý do (ví dụ như sẽ không cần đồng bộ hóa rõ ràng và sẽ có một cơ chế được xác định để mở rộng FILE* cho các hệ thống do người dùng xác định) nhưng tôi không biết bất kỳ hệ thống nào thực sự thực hiện điều này . Có một triển khai thực hiện là một trình bao bọc khác có thể thực hiện và thực hiện <iostream> s trong điều khoản của <stdio> là một lựa chọn thực hiện điển hình mặc dù nó có nhược điểm là nó giới thiệu thêm chi phí cho các hoạt động nhất định và hầu hết các thư viện chuẩn C++ đã chuyển sang sử dụng hoàn toàn riêng biệt triển khai.

Thật không may, cả việc gói gọn và triển khai độc lập đều có chung một vấn đề: I/O là không hiệu quả khi thực hiện một cấp độ ký tự. Vì vậy, nó chủ yếu là bắt buộc để đệm ký tự và đọc từ hoặc ghi vào một bộ đệm. Điều này hoạt động tốt cho các luồng độc lập với nhau.Việc nắm bắt được các tiêu chuẩn C suối stdin, stdout, stderr và C++ hẹp các đối tác nhân vật của mình std::cin, std::cout, std::cerr/std::clog và C++ đối tác nhân vật rộng std::wcin, std::wcout, std::wcerr/std::wclog, tương ứng: những gì sẽ xảy ra khi một người dùng đọc cả từ stdinstd::cin? Nếu một trong các luồng này đọc một bộ đệm các ký tự từ luồng hệ điều hành cơ bản thì các lần đọc sẽ xuất hiện không đúng thứ tự. Tương tự, nếu cả hai stdoutstd::cout các ký tự bộ đệm độc lập được sử dụng sẽ xuất hiện theo thứ tự không mong muốn khi người dùng viết cả hai luồng. Do đó, có các quy tắc đặc biệt về đối tượng luồng chuẩn C++ (tức là std::cin, std::cout, std::cerrstd::clog và các đối tượng ký tự rộng) yêu cầu chúng đồng bộ hóa với đối tác tương ứng <stdio> tương ứng. Có hiệu quả, điều này có nghĩa là cụ thể các đối tượng C++ này hoặc sử dụng một cách thực hiện phổ biến trực tiếp hoặc chúng được triển khai theo các điều khoản <stdio> không đệm bất kỳ ký tự nào. Nó được nhận ra rằng chi phí của việc đồng bộ hóa này khá đáng kể nếu việc triển khai không chia sẻ cơ sở chung và có thể không cần thiết đối với một số người dùng: nếu người dùng chỉ sử dụng <iostream> anh ta không muốn trả thêm tiền và, quan trọng hơn, anh ta không muốn trả tiền cho các chi phí phụ được áp đặt bởi không sử dụng một bộ đệm. Để thực hiện cẩn thận chi phí không sử dụng bộ đệm có thể khá đáng kể vì nó có nghĩa là một số hoạt động nhất định phải thực hiện kiểm tra và có thể gọi hàm ảo trong mỗi lần lặp thay vì chỉ một lần trong một thời gian. Do đó, std::sync_with_stdio() có thể được sử dụng để tắt tính năng đồng bộ hóa này có thể có nghĩa là các đối tượng luồng chuẩn thay đổi hoàn toàn việc triển khai nội bộ của chúng. Vì các bộ đệm luồng của các đối tượng dòng tiêu chuẩn có thể được thay thế bởi người dùng, nên không thể thay thế bộ đệm luồng nhưng việc thay thế nội bộ bộ đệm luồng có thể được thay đổi.

Việc triển khai tốt thư viện <iostream> tất cả điều này chỉ ảnh hưởng đến các đối tượng dòng chuẩn. Tức là, các luồng tệp phải hoàn toàn không bị ảnh hưởng bởi điều này. Tuy nhiên, nếu bạn muốn sử dụng các đối tượng luồng chuẩn và muốn đạt được hiệu suất tốt, rõ ràng bạn không muốn trộn <stdio><iostream> và bạn muốn tắt đồng bộ hóa. Đặc biệt, khi so sánh hiệu suất I/O giữa <stdio><iostream>, bạn nên biết điều này.

+0

+1, giải thích tuyệt vời. – Xeo

+0

Không thể tìm thấy giải thích tốt hơn về kịch bản. Tôi đã từng bước vào cái bẫy này và dành 3 ngày để hiểu chuyện gì đã xảy ra. Làm tốt lắm, Dietmar! –

3

Trên thực tế, stdout, stderrstdin là các trình xử lý tệp của OS. Và cấu trúc FILE của C cũng như iostream các lớp của C++ là cả hai trình bao bọc của các trình xử lý tệp đó. Cả hai lớp iostream và cấu trúc FILE có thể có bộ đệm riêng của chúng hoặc một thứ khác cần được đồng bộ giữa nhau để đảm bảo rằng đầu vào từ tệp hoặc đầu ra cho tệp được thực hiện đúng.

+0

'stdin' và bạn bè là cấu trúc của kiểu' FILE' và 'C'. Hệ điều hành chỉ đơn giản có các bộ mô tả tập tin '0',' 1' và '2', như được định nghĩa trong' unistd.h' là '#define STDIN_FILENO 0'. – hochl

+0

chính xác.Nhưng cũng stdin, stdout vv là chữ viết tắt của "dòng đầu vào tiêu chuẩn", "dòng đầu ra tiêu chuẩn" vv tương ứng. Tôi đã sử dụng những từ này làm chữ viết tắt cho các luồng hệ thống. – Jurlie

2

OK, đây là những gì tôi đã tìm thấy.

Thực ra, I/O cuối cùng được thực hiện bởi các cuộc gọi và chức năng hệ thống gốc.

Bây giờ, hãy lấy Microsoft Windows làm ví dụ. Trên thực tế, có các tay cầm có sẵn cho STDIN, STDIO vv (xem here). Về cơ bản, cả hai hàm C++ iostream và C stdio đều gọi hàm gốc, C++ iostream không bọc các hàm I/O của C (trong các triển khai hiện đại). Nó gọi các phương thức hệ thống riêng trực tiếp.

Ngoài ra, tôi thấy điều này:

Khi stdin, stdout, và stderr được chuyển hướng, chức năng C chuẩn như printf() và được() có thể được sử dụng, mà không cần thay đổi, để giao tiếp với Win32 giao diện điều khiển. Nhưng những gì về C + + I/O suối? Vì cin, cout, cerr và clog có liên quan chặt chẽ với stdin, stdout và stderr của C, bạn có thể mong đợi chúng hoạt động tương tự. Điều này là một nửa bên phải.

Dòng C++ I/O thực sự có hai loại: mẫu và không phải mẫu. Phiên bản I/O cũ không có mẫu của các luồng I/O đang dần được thay thế bằng một kiểu luồng mới hơn được định nghĩa bởi Thư viện mẫu chuẩn (STL) và hiện đang được hấp thụ vào tiêu chuẩn ANSI C++. Visual C++ v5 cung cấp cả hai loại và cho phép bạn chọn giữa hai loại này bằng cách bao gồm các tệp tiêu đề khác nhau. Các luồng I/O STL hoạt động như bạn mong đợi, tự động sử dụng bất kỳ xử lý stdio mới được chuyển hướng nào. Tuy nhiên, các luồng I/O không phải mẫu không hoạt động như mong đợi. Để khám phá lý do tại sao, tôi nhìn vào mã nguồn, thuận tiện được cung cấp trên đĩa CD-ROM Visual C++.

Vấn đề là các luồng I/O cũ hơn được thiết kế để sử dụng "mô tả tập tin kiểu UNIX", trong đó các số nguyên được sử dụng thay vì xử lý (0 cho stdin, 1 cho stdout, vv). Điều đó thuận tiện cho việc triển khai UNIX, nhưng trình biên dịch Win32 C phải cung cấp thêm một lớp I/O khác để biểu diễn kiểu I/O đó, vì Win32 không cung cấp một bộ các hàm tương thích. Trong bất kỳ trường hợp nào, khi bạn gọi hàm _open_osfhandle() để liên kết một trình xử lý Win32 mới với (ví dụ) stdout, nó không có hiệu lực trên lớp I/O khác. Do đó, bộ mô tả tập tin 1 sẽ tiếp tục sử dụng cùng một trình xử lý Win32 cơ bản như trước đây và việc gửi đầu ra tới cout sẽ không tạo ra hiệu ứng mong muốn.

May thay, các nhà thiết kế gói luồng I/O ban đầu đã dự đoán được vấn đề này và cung cấp giải pháp sạch và hữu ích. Lớp ios cơ sở cung cấp một hàm tĩnh, sync_with_stdio(), làm cho thư viện thay đổi các mô tả tệp cơ bản của nó để phản ánh bất kỳ thay đổi nào trong lớp I/O tiêu chuẩn. Mặc dù điều này là không cần thiết cho các luồng I/O STL, nó không gây hại và cho phép tôi viết mã hoạt động chính xác với dạng luồng I/O mới hoặc cũ.

(source)

Do đó gọi sync_with_stdio() thực sự thay đổi các file descriptor cơ bản. Thực tế nó được các nhà thiết kế thêm vào để đảm bảo khả năng tương thích của C++ I/O cũ hơn với các hệ thống như Windows-32 sử dụng các chốt xử lý thay vì các số nguyên.

Lưu ý rằng việc sử dụng sync_with_stdio() là không cần thiết với C++ STL I/O dựa trên mẫu C++ hiện đại.

+1

Có thể đáng lưu ý rằng các cuộc đàm phán nguồn của bạn về "Visual C++ 5" và "CD-ROM", chỉ để đặt điều đó trong ngữ cảnh. –

+0

@KerrekSB đó là vì nó được viết vào những năm 90. :). Khi STL là một 'điều mới'. – ApprenticeHacker

1

Một thể là một wrapper xung quanh khác (và làm việc cả hai cách. Bạn thể thực hiện stdio chức năng bằng cách sử dụng iostream và ngược lại. Hoặc bạn có thể viết chúng hoàn toàn độc lập.

sync_with_stdio đảm bảo rằng hai luồng sẽ được đồng bộ hóa nếu nó được bật nhưng chúng vẫn có thể đồng bộ hóa khi nó bị vô hiệu hóa, nếu thực sự muốn.

Nhưng ngay cả khi một là một trình bao bọc xung quanh, người khác vẫn có thể có bộ đệm người khác không chia sẻ, ví dụ, để đồng bộ hóa vẫn cần thiết.

+0

Nhưng theo tài liệu tham khảo tôi đăng trong câu trả lời của tôi, người viết cho biết I/O dựa trên UNIX cũ, sử dụng các số nguyên thay vì xử lý cho các bộ mô tả tập tin, gây ra sự không tương thích với Windows, vì vậy các nhà thiết kế đã thêm 'sync_with_stdio'. Nó không phải là vấn đề tương thích? – ApprenticeHacker

+0

Nó không có gì để làm với uno-phong cách io (tập tin mô tả), mà sẽ không bao giờ được đồng bộ hóa với iostream trừ khi bạn hoàn toàn vô hiệu hóa đệm. Nó chỉ liên quan đến stdio (cấp độ 'FILE'). –

1

Họ điều tương tự, nhưng chúng cũng có thể được đệm riêng biệt. Điều này có thể ảnh hưởng đến mã pha trộn việc sử dụng C và C++ I/O, như thế này

std::cout << "Hello "; 
printf("%s", "world"); 
std::cout << "!\n"; 

Để làm việc này, các luồng cơ bản phải được đồng bộ hóa bằng cách nào đó. Trên một số hệ thống, này có thể có nghĩa là hiệu suất có thể bị ảnh hưởng. Vì vậy, tiêu chuẩn cho phép bạn gọi số std::sync_with_stdio(false) để nói rằng bạn không quan tâm đến mã như thế này, nhưng muốn có các luồng chuẩn hoạt động nhanh nhất có thể nếu nó tạo sự khác biệt. Trên nhiều hệ thống, nó không tạo ra sự khác biệt.