30

C++ で使用されるストリームと標準 C の一部であるストリームとのios_base::sync_with_stdio間の同期を基本的にオフにする (既にオフにしている場合はオンにする) ことができる関数の存在を知りました。iostreamcstdio

さて、私は常に、C のとはstdout、基本的に C++ の iostream クラスのオブジェクトのセットにラップされていると考えていました。しかし、それらが互いに同期する必要がある場合、これは C++ のクラスがCなど のラッパーで はないことを示しています。stderrstdiniostreamstdin

私はこれでかなり混乱していますか?C++ の iostream と C の stdio が、抽象化のレベルが異なるだけで、まったく同じことを行う別のものであることを誰かが明確にすることはできますか? 同じ物だと思ってた!?

それらが同期されなければならないのはどうしてですか?私はいつも、それらは本質的に同じものであり、一方が他方を包み込んでいると思っていました。

4

5 に答える 5

38

C および C++ 標準では、特定の操作の効果についてのみ、実装方法に関する要件はありません。<stdio>vs.機能の場合、<iostream>これは、一方が他方をラップできること、両方が本質的に同じであること、または完全に独立していることを意味します。技術的には、共通の実装を使用することがいくつかの理由で理想的です (たとえば、明示的な同期の必要がなくFILE*、ユーザー定義システム用に拡張するメカニズムが定義されているなど)、実際にこれを行うシステムを私は知りません。一方の実装を他方のラッパーにすることが可能<iostream>であり、<stdio>は典型的な実装の選択でしたが、特定の操作に余分なコストがかかるという欠点があり、ほとんどの C++ 標準ライブラリは完全に別の実装を使用するようになりました。

残念ながら、ラップされた実装と独立した実装の両方に共通の問題があります。つまり、I/O は 1 文字レベルで行うと非常に非効率的です。したがって、文字をバッファリングし、バッファから読み書きすることは基本的に必須です。これは、互いに独立したストリームに対してうまく機能します。キャッチは、標準 C ストリームstdinstdoutstderrおよびそれらに対応する C++ ナロー文字std::cinstd::coutstd::cerr/std::clogおよび C++ ワイド文字対応std::wcinstd::wcoutstd::wcerr/です。std::wclogユーザーが と の両方から読み取るstdinとどうなりstd::cinますか? これらのストリームのいずれかが基になる OS ストリームから文字のバッファを読み取ると、読み取りは順不同に見えます。同様に、stdoutstd::coutユーザーが両方のストリームに両方を書き込むと、独立したバッファ文字が予期しない順序で表示されます。その結果、標準 C++ ストリーム オブジェクト (つまりstd::cinstd::coutstd::cerr、 、およびstd::clog対応するワイド文字) には、それぞれ<stdio>の対応するオブジェクトと同期することを義務付ける特別な規則があります。事実上、これは具体的には、これらの C++ オブジェクトが共通の実装を直接使用するか、文字に関して実装されており、文字をバッファリング<stdio> ないことを意味します。

実装が共通のベースを共有していない場合、この同期のコストは非常に大きく、一部のユーザーにとっては不要である可能性があることが認識され<iostream>ました。 、彼はバッファを使用しないことによって課せられる余分なコストを支払いたくありません。注意深い実装では、バッファを使用しないことのコストは非常に大きくなる可能性があります。これは、特定の操作が、時々ではなく、反復ごとにチェックと、場合によっては仮想関数呼び出しを実行する必要があることを意味するためです。したがって、std::sync_with_stdio()この同期をオフにするために使用できます。これは、標準ストリーム オブジェクトが内部実装を多かれ少なかれ完全に変更することを意味する場合があります。標準ストリーム オブジェクトのストリーム バッファはユーザーが置き換えることができるため、残念ながらストリーム バッファを置き換えることはできませんが、ストリーム バッファの内部実装は変更できます。

ライブラリの適切な実装では、<iostream>これはすべて標準ストリーム オブジェクトにのみ影響します。つまり、ファイル ストリームはこれによってまったく影響を受けないはずです。ただし、標準のストリーム オブジェクトを使用し、優れたパフォーマンスを達成したい場合は、明らかに混合<stdio><iostream>たくなく、同期をオフにする必要があります。特に、 と の I/O パフォーマンスを比較する場合は、この点に注意する必要があります<stdio><iostream>

于 2012-03-11T10:30:46.297 に答える
3

実際には stdoutstderrstdinは OS のファイル ハンドラです。そしてFILE、C の構造とiostreamC++ のクラスは、どちらもこれらのファイル ハンドラのラッパーです。iostream クラスと FILE 構造の両方に、ファイルからの入力またはファイルへの出力が正しく行われるようにするために、相互に同期する必要がある独自のバッファーまたはその他のものがある場合があります。

于 2012-03-11T09:25:04.823 に答える
2

さて、これが私が見つけたものです。

実際、I / Oは、最終的にはネイティブのシステムコールと関数によって実行されます。

ここで、MicrosoftWindowsを例にとってみましょう。STDIN、などの実際に使用可能なハンドルがありますSTDIOここを参照)。したがって、基本的に、C ++iostreamとCはどちらもstdioネイティブシステム関数を呼び出しますが、C++iostreamはCのI/O関数をラップしません(最新の実装では)。ネイティブシステムメソッドを直接呼び出します。

また、私はこれを見つけました:

stdin、stdout、およびstderrがリダイレクトされると、printf()やgets()などの標準C関数を変更せずに使用して、Win32コンソールと通信できます。しかし、C ++ I / Oストリームはどうですか?cin、cout、cerr、およびclogは、Cのstdin、stdout、およびstderrと密接に関連しているため、同様に動作することが期待されます。これは半分正しいです。

C ++ I / Oストリームには、実際にはテンプレートと非テンプレートの2つの種類があります。古い非テンプレートバージョンのI/Oストリームは、標準テンプレートライブラリ(STL)によって最初に定義され、現在ANSIC++標準に吸収されている新しいテンプレートスタイルのストリームに徐々に置き換えられています。Visual C ++ v5は両方のタイプを提供し、異なるヘッダーファイルをインクルードすることで2つから選択できます。STL I / Oストリームは、新しくリダイレ​​クトされたstdioハンドルを自動的に使用して、期待どおりに機能します。ただし、非テンプレートI/Oストリームは期待どおりに機能しません。その理由を見つけるために、Visual C++CD-ROMで便利に提供されているソースコードを調べました。

問題は、古いI / OストリームがUNIXスタイルの「ファイル記述子」を使用するように設計されていることです。ハンドルの代わりに整数が使用されます(stdinの場合は0、stdoutの場合は1など)。これはUNIX実装には便利ですが、Win32は互換性のある関数のセットを提供しないため、Win32CコンパイラはそのスタイルのI/Oを表すためにさらに別のI/O層を提供する必要があります。いずれの場合も、_open_osfhandle()を呼び出して新しいWin32ハンドルを(たとえば)stdoutに関連付けると、I/Oコードの他の層には影響しません。したがって、ファイル記述子1は、以前と同じ基になるWin32ハンドルを引き続き使用し、出力をcoutに送信しても目的の効果は得られません。

幸い、元のI / Oストリームパッケージの設計者はこの問題を予見し、クリーンで有用なソリューションを提供しました。基本クラスiosは、静的関数sync_with_stdio()を提供します。これにより、ライブラリは、標準I / Oレイヤーでの変更を反映するために、基になるファイル記述子を変更します。これはSTLI/ Oストリームに厳密に必要というわけではありませんが、害はなく、新しい形式または古い形式のI/Oストリームのいずれかで正しく機能するコードを記述できます。

ソース

したがって、呼び出すとsync_with_stdio()、基になるファイル記述子が実際に変更されます。実際、古いC ++ I / Oと、整数の代わりにハンドルを使用するWindows-32などのシステムとの互換性を確保するために、設計者によって追加されました。

sync_with_stdio()最新のC++テンプレートベースのSTLI/ Oでは、使用する必要がないことに注意してください。

于 2012-03-11T09:33:54.847 に答える
1

一方を他方のラッパーにすることができます(これは両方の方法で機能します。を使用して関数を実装することも、その逆も可能です。または、完全に独立して関数を作成することもできます。stdioiostream

またsync_with_stdio、有効になっている場合、2つのストリームが同期されることを保証します。ただし、本当に必要な場合は、無効にしても同期できます。

ただし、一方が他方のラッパーである場合でも、たとえば、一方が他方が共有しないバッファーを持っている可能性があるため、同期が必要です。

于 2012-03-11T09:38:17.250 に答える
1

それら同じものですが、別々にバッファリングされる場合もあります。これは、次のように、C と C++ の I/O を混合して使用するコードに影響を与える可能性があります。

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

これが機能するには、基礎となるストリームが何らかの方法で同期されている必要があります。一部のシステムでは、これはパフォーマンスが低下する可能性があることを意味する場合があります。

そのため、標準では、このようなコードは気にしないが、標準ストリームが違いを生む場合はstd::sync_with_stdio(false)できるだけ速く動作することを好むと言うために呼び出すことができます。多くのシステムでは違いはありません。

于 2012-03-11T10:30:55.947 に答える