|
本サイトは移転しました。旧アドレスからのリダイレクトは2025年03月31日(月)まで有効です。
|
🛈 | ✖ |
コンソールアプリケーションのGNU gettextによる国際化機能でのロケールの扱いについてソースコードで確認する。
mingw-w64実装でGNU gettext libintlライブラリによる翻訳文字列をコマンドプロンプトへ出力する方法を検討する。
これを正しく翻訳文字列として出力するにはロケールを事前設定する。選択される翻訳ファイルは[ドメインパス]\[ロケール]\[カテゴリ]\[ドメイン].moで、例えば[ドメインパス]\ja_JP\LC_MESSAGES\Console1.moとなる。ロケール名はウィンドウズ準拠でなくPOSIX準拠を採る。
ドキュメント(GNU gettext utilities 4.2 Triggering gettext Operations)はsetlocale(LC_ALL,"")でC言語ロケールを設定せよとするが、これはロケールをLANGなどの環境変数から取得するPOSIX互換環境を前提とする。ウィンドウズもLANGなどに加えてOUTPUT_CHARSET環境変数にコマンドプロンプトのコードページ(整数値)に対応する文字コード名(例えば932に対して"CP932")を与えれば良いとするが(GNU gettext utilities 2.4 Obtaining good output in a Windows console)、mingw-w64では日本語の翻訳文字列(シフトJIS)の出力がうまく行かない。そもそもウィンドウズネイティブのロケールはスタートメニュー[設定|時刻と言語|地域|...]が設定するもの(ウィンドウズ設定ロケール)で環境変数の利用は好ましくない。
本記事はC言語ロケール、ウィンドウズ設定ロケール、翻訳文字列出力コードページ、コマンドプロンプトコードページの四つを出力に影響する要素として検討する。翻訳文字列のコマンドプロンプト出力は以下の3ステップとなる。翻訳ファイル文字コードで記述された翻訳文字列はコマンドプロンプト出力に至るまで一回または二回コードページ(文字コード)変換される。コードページ変換はC言語ロケールLC_CTYPEカテゴリー(LC_CTYPEロケール)に従う。
第一ステップで翻訳ファイルが見つからない、あるいは第二ステップでコードページ変換に失敗すると検索文字列をそのまま出力する。問題は第三ステップで、LC_CTYPEロケールが"C"以外の場合にシフトJISの出力を失敗する。通常の日本語環境なら出力とコマンドプロンプトのコードページが一致するがそれでも失敗する。LC_CTYPEロケールをコードページに対して正しく設定した(例えば"Japanese_Japan.932")としても事情は変わらない。この失敗はコマンドプロンプトだけでファイルへのリダイレクトは問題ない。
各ステップ毎に実装を調査する。最初の2ステップはlibintlライブラリgettext関数が行う。ソースコードは入手可能で調査に利用する。最後の1ステップはC++/C言語標準ライブラリの範疇でコマンドプロンプトへの出力はmsvcrtランタイムが行う。ソースコードは入手できずテスト動作から推測するため正確性は劣る。
libintlライブラリのソースコードはGNU gettextの一部としてgettext-runtime\intlディレクトリに展開される。以降、ファイル相対パスは断らない限りgettext-runtime\intlからとする。mingw-w64をターゲットとする部分のみを参照する。調査はバージョン0.21.1で行った。
libintlライブラリのクライアントはlibintl.hをインクルードするが、libintl.hはパッケージの一部としてMSYS2インクルード(例えばC:\msys64\mingw64\include)に存在する。一方でダウンロードしたソースコードは雛形としてlibgnuintl.in.hを含みautotoolsツールでlibintl.hとlibgnuintl.hを生成する。libgnuintl.hはlibintl.hとほぼ同一でライブラリ内部で用いる。本記事はMSYS2インクルードのlibintl.hを参照するがダウンロードのそれと同一と見て問題ない。
libintlライブラリの公開関数(例えばgettext)は全てlibintl_を前置して定義(libintl_gettext)してlibintl.hがリダイレクトする。さらに重要な役目としてsetlocale関数を置換する。setlocaleは標準ライブラリの一部でC言語ロケールを設定/取得する(JTC1/SC22/WG14 N1570 7.11.1.1)。POSIXはこれをメッセージ言語の指定にも拡張してLC_MESSAGESカテゴリを追加し(IEEE 1003.1-2017 setlocale)、gettextもその利用を前提とする。mingw-w64のsetlocale実装はmsvcrtランタイムでPOSIXをカバーせず、定義を置換して機能拡張する。libintl_setlocale関数がそれで、libintl.hはこれをsetlocaleに#defineする。つまりlibintl.hインクルードの有無でsetlocaleの定義が異なる。
libintl.hはsetlocaleをlibintl_setlocale(setlocale.c:1389)に#defineして機能拡張する。libintl.hをインクルードするクライアントがsetlocaleをコールするとlibintl_setlocaleをコールする。gettextソースコードからsetlocaleをコールする場合もlibintl.h/libgnuintl.hをインクルードしていればlibintl_setlocale、そうでなければmsvcrtランタイムのsetlocaleとなる。
libintl_setlocaleはsetlocale_unixlike関数(setlocale.c:647)あるいはsetlocale_single関数(setlocale.c:847)をコールしてロケールを設定/取得する。setlocale_unixlikeはロケール名にPOSIX準拠とウィンドウズ準拠の両方に対応してPOSIX準拠はウィンドウズ準拠に変換するが、LC_MESSAGESは扱えない。setlocale_singleはLC_MESSAGESの場合は特別な処理を行い、そうでなければsetlocale_unixlikeをコールする。libintl_setlocaleの処理は一貫性に欠け、LC_ALLがLC_MESSAGESも含めて変更するのは""に限られる。LANGなどの環境変数が設定されていないとして、libintl.hをインクルードした場合のsetlocale(すなわちlibintl_setlocale)の日本語環境での挙動をまとめる。ただし後述するがsetlocaleは翻訳ファイルの選択に全く寄与しない。
LC_MESSAGES以外 | LC_MESSAGES | |
---|---|---|
初期状態 | C | C |
setlocale(LC_ALL,"ja_JP") | Japanese_Japan.932 | 変更しない |
setlocale(LC_ALL,"Japanese_Japan.932") | Japanese_Japan.932 | 変更しない |
setlocale(LC_ALL,"") | Japanese_Japan.932 | ja_JP |
setlocale(LC_MESSAGES,"ja_JP") | 変更しない | ja_JP |
setlocale(LC_MESSAGES,"Japanese_Japan.932") | 変更しない | Japanese_Japan.932 |
setlocale(LC_MESSAGES,"") | 変更しない | ja_JP |
LC_MESSAGES以外のロケールは常にウィンドウズ準拠名になる。特に重要なものとして、LC_CTYPEロケールが使用する文字コードは文字コード名でなくコードページ(整数値)で指定される。
ファイル(setlocale.c)はlibgnuintl.hをインクルードするのでsetlocaleを#undefしてmsvcrtランタイムに戻す。これは当たり前の話でさもなくば無限回帰になる。ロケール取得やエラー処理などは省いた。
setlocaleはmsvcrtランタイムをコールする。POSIX準拠名からウィンドウズ準拠名への変換は省略した。
LC_MESSAGESはmsvcrtラインタイムのsetlocaleを使えないので自ら処理してグローバルに記憶する。
libintlライブラリは公開関数(例えばgettext)を全てlibintl_を前置した関数(libintl_gettext)にリダイレクトする。リダイレクト関数を定義するソースコードファイル(gettext.c)は対応するマクロ(GETTEXT)を定義し、ファイル内の定義/参照はマクロ名で行う。gettext(またはGETTEXT)(gettext.c:49)はdcgettext(またはDCGETTEXT)(dcgettext.c:42)をコールし、dcgettextはdcigettext(またはDCIGETTEXT)(dcigettext.c:454)をコールする。まとめればgettext(msgid)はdcigettext(NULL,msgid,NULL,0,0,LC_MESSAGES)をコールする。dcigettextは内部関数だが公開関数に準じた命名(libintl_dcigettext)とマクロ定義(DCIGETETXT)を行っている。
dcigettextは内部関数で単数形gettext/dcgettextと複数形ngettext/dcngettextが共にコールする。翻訳文字列をテーブル保存して再利用する部分、翻訳ファイルの探索とバインド、複数形への対応などは省略した。カテゴリはcategory仮引数に指定するがLC_MESSAGESのみを想定して良く、ドキュメントもそれ以外の使い道は想像できないとする(GNU gettext utilities 11.2.2 Solving Ambiguities)。
翻訳ファイルはguess_category_value関数(dcigettext.c:1125)で得るロケール名配列から_nl_find_domain関数(finddomain.c:57)で選択する。guess_category_valueが配列を返すのはデフォルトロケールを複数持つシステムへの対応で、ウィンドウズ実装で要素数は1に限定される。guess_category_valueの処理を説明する。なお_nl_locale_name_XXXはgettextP.hでgl_locale_name_XXXマクロに定義され、ソースコードファイル中はマクロ名で定義/参照する。ここのロケール名は全てウィンドウズ準拠(例えばJapanese_Japan)でなくPOSIX準拠(ja_JP)で取得する。
_nl_locale_name_posix(またはgl_locale_name_posix)が示すように、翻訳ファイルの選択にsetlocale(LC_MESSAGES,...)は全く寄与しない。例えば日本語(日本)ロケールならクライアントがsetlocale(LC_MESSAGES,"en_US")をコールしたとしても日本語に翻訳される。
ファイル(localename.c)はlibintl.h/gnuliblintl.hをインクルードせず、setlocaleはmsvcrtランタイムとなる。従ってLC_MESSAGESカテゴリ以外だけsetlocale(category,NULL)をコールしウィンドウズ準拠名を返せばPOSIX準拠名に変換して返す。LC_MESSAGESの場合またはsetlocaleがウィンドウ準拠名を返さなかった場合、LANGあるいはLC_XXX環境変数から取得する。これがウィンドウズ準拠名ならPOSIX準拠名に変換して返し、そうでなければそのままで返す。取得できなければ取得失敗としてNULLを返す。
LC_MESSAGESをsetlocale(LC_MESSAGES,NULL)を使わず常に環境変数から取得する理由はソースコードコメントに示されていて、クライアントがlibintl.hをインクルードしない場合の備えだそうだ。setlocaleにLC_MESSAGESを加える苦労は結局何の意味も無かったのか。
デフォルトロケールはウィンドウズAPI関数GetThreadLocaleで得られるロケールをPOSIX準拠名に変換したものとする。
_nl_find_msg関数(dcigettext.c:945)は検索文字列に対応する翻訳文字列を出力コードページ(整数値)に変換して返す。_nl_find_msgはget_output_charset関数(dcigettextc:1669)をコールする。get_output_charsetは翻訳文字列を取得して出力コードページに変換する。デフォルトの出力コードページはlocale_charset関数(localecharset.c:827)で取得する。
検索文字列から翻訳文字列を取得する部分は全て割愛し、翻訳文字列を出力コードページに変換する部分だけを説明する。変換後の翻訳文字列をテーブル保存して再利用する部分や複数形処理も省略した。iconvライブラリを利用するため出力コードページからiconv準拠の文字コードを取得し、iconv関数で翻訳ファイルの文字コードから変換する。出力文字コードの取得はget_output_charset関数が行う。
get_output_charsetは_nl_find_msgからコールされて以下の優先順位で出力文字コードを返す。
OUTPUT_CHARSET環境変数から得る文字コードは1度だけ取得してローカルスタティックに保持する。なおbind_textdomain_codesetやOUTPUT_CHARSETから得るとLC_CTYPEロケールとの一致が保証されずコマンドプロンプト出力が文字化けする可能性がある。
ファイル(localecharset.c)はlibintl.h/gnuliblintl.hをインクルードせず、setlocaleはmsvcrtランタイムとなる。LC_CTYPEロケールが"C"でないウィンドウズ準拠名であればコードページ文字列を取得してCP前置した出力文字コードとする。これが取得できない場合はウィンドウズAPI関数のGetACPでシステムロケールのコードページを取得してCP前置した出力文字コードとする。これがiconv準拠でない場合はさらに準拠名へ変換するが、その部分は省いた。
libintlのmingw-w64実装は二箇所でウィンドウズAPI関数をコールする。一つは_nl_locale_name_default(またはgl_locale_name_default)のGetThreadLocale関数で、もう一つはlocale_charsetのGetACP関数である。それぞれオペレーティングシステムから設定を取得する。
GetThreadLocaleはスレッドのウィンドウズ設定ロケールを返し、libintlはLANGなどの環境変数が設定されていない時にこれをロケールとして取得する。この値はSetThreadLocale関数で設定できる。SetThreadLocaleドキュメントは初期値をユーザーデフォルトロケールとするが、そうでないケースが存在する。例えばシステムロケール英語(米国)であれば初期値はユーザーデフォルトロケールだが、日本語(日本)であればシステムロケールとなる。GetThreadLocale/SetThreadLocaleは言語コード識別子(LCID)値でロケールを扱うもののLCIDは非推奨となってリリース間で安定しない。GetThreadLocaleが意図通りの値を間違いなく返すには事前にSetThreadLocaleをコールする。SetThreadLocale(LOCALE_USER_DEFAULT)をコールすればGetThreadLocaleは間違いなくユーザーデフォルトロケールを返し、SetThreadLocale(LOCALE_SYSTEM_DEFAULT)をコールすればシステムロケールを返す。
GetACPはシステムロケールのコードページ(整数値)を返し、これはコードページ(手法)を採るデスクトップアプリケーションのコードページ(整数値)(いわゆる"ANSI"コードページ)であるが、libintlはこれを翻訳文字列の出力文字コードとして取得する。ところでコマンドプロンプトのデフォルトコードページ(いわゆる"OEM"コードページ)を返すのはGetOEMCP関数で、コンソールアプリケーションは主にコマンドプロンプトへ出力する。なおコマンドプロンプトのコードページはCHCPコマンドで任意に変更できる。日本語(日本)で"ANSI"コードページと"OEM"コードページは等しい(932)が、例えば英語(米国)で異なり(1252と437)ASCII外で同じ文字に対応しない。
mingw-w64でのlibintlによる翻訳にsetlocale(LC_MESSAGES,...)は寄与せずコールの必要は無い事を述べてきた。ここからはsetlocale(LC_CTYPE,...)がシフトJIS文字列のコマンドプロンプト出力を阻害する問題を議論する。この問題はlibintlと直接関係しないがGNU gettextドキュメントを盲信してsetlocale(LC_ALL,"")すると日本語の翻訳文字列がコマンドプロンプトに出力されない。つまりドキュメントに反しsetlocale(LC_ALL,"")はコールすべきでないが、別の理由でsetlocale(LC_CTYPE,...)を必要とするかもしれない。登場人物はC++標準ライブラリ(GCC libstdc++)、libintlライブラリ(GNU gettext)、C言語標準ライブラリ(msvcrtランタイム)、ウィンドウズAPI関数で、全員が2バイト文字コードにまともな対応をしてこなかった結果のようだ。勇気を出してこの沼に飛び込もう。
以下、断らない限り日本語環境でのコマンドプロンプト出力を前提とする。問題はlibintlの翻訳と無関係なのでサンプルコードに日本語文字列を直接書き込む。コンパイルオプションに-finput-charset=ソースコード物理ファイル文字コード(CP932あるいはUTF-8)と-fexec-charset=CP932を加える。libintl.hをインクルードする場合は適切なライブラリファイル(例えばlibintl.a)を追加する。
これ以上ありえないシンプルなソースコードにsetlocale(LC_CTYPE,"")しただけで出力は失敗する。
ASCII文字列、C言語標準ライブラリprintf関数、libintl.hインクルード、sync_with_stdio(false)コールを検討水準に加える。coutが出力エラーとなる場合に備え都度clearメンバ関数をコールしている。
結果をまとめる。全てにおいてASCII(あるいはANK)文字列は問題ないが、シフトJIS文字列の結果は混乱する。シフトJIS文字列出力に失敗しても先頭にASCII(ANK)を追加すれば成功する場合がある。printfはlibintl.hインクルード有無で失敗パターンが異なる。この段階でシフトJIS文字列が常に成功するのはsync_with_stdio(false)コール後のC++標準出力coutだけのようだ。
関数 | 条件 | 文字列 | 出力 | 結果 | 実装 |
---|---|---|---|---|---|
printf | libintl.hインクルードしない | Hello | Hello | 成功 | fputcループ(推測) |
こんにちは | アノソヘ | 失敗、文字化け | |||
xこんにちは | xアノソヘ | 失敗、文字化け | |||
libintl.hインクルードする | Hello | Hello | 成功 | fwrite | |
こんにちは | (エラー) | 失敗、出力エラー | |||
xこんにちは | xこんにちは | 成功 | |||
cout | sync_with_stdio(false)コールしない | Hello | Hello | 成功 | |
こんにちは | (エラー) | 失敗、出力エラー | |||
xこんにちは | xこんにちは | 成功 | |||
sync_with_stdio(false)コールする | Hello | Hello | 成功 | write | |
こんにちは | こんにちは | 成功 | |||
xこんにちは | xこんにちは | 成功 |
libintl.hはprintfをlibintl_printf(printf.c:153)に#defineする。これはlibintl_vprintfを経由してlibintl_vfprintf(printf.c:104)をコールしてC言語標準ライブラリfwrite関数(N1570 7.21.8.2)で出力する。sync_with_stdio(false)はcoutのストリームバッファを切り替える。コールしなければstdio_sync_filebufクラス([MSYS2インクルード]/c++/[mingw-w64バージョン番号]/ext/stdio_sync_filebuf.h)、コールすればstdio_filebufクラス(.../ext/stdio_filebuf.h)を用いる。stdio_sync_filebufはC言語標準出力とバッファを共有し、バッファ書き込み(xsputnメンバ関数)はfwriteで出力し、バッファフラッシュ(overflowメンバ関数)はC言語標準出力をフラッシュする。stdio_filebufはbasic_filebufクラス(.../bits/fstream.tcc)を継承しxsputnは自ら所有するバッファへ書き込み、overflowは_M_convert_to_externalメンバ関数を経由して__basic_fileクラス(.../bits/basic_file.h)のxsputnメンバ関数がPOSIXのwrite関数(IEEE 1003.1-2017 pwrite)で直接出力する。writeはユニックスライクではシステムコールだがウィンドウズはmsvcrtランタイムが供給する。なお__basic_file::xsputnはソースコードファイル([libstdc++ソースコード]/basic_file.cc)が実装するため調査はリンク先で行った。
C言語標準ライブラリfwrite関数とPOSIXのwrite関数の違いの一つは、前者がC言語標準出力バッファへ書き込むのに対し後者は出力先へ直接書き込む。stdio_filebufクラスは自前のバッファを所有してC言語バッファをバイパスする必要があり、これを理由としてfwriteでなくwriteを使用する。
libintl.hをインクルードしないprintfはmsvcrtランタイムでソースコードの調査手段が無くテストコードで推測するが、C言語標準ライブラリで1符号ずつ出力すれば同様の文字化けが発生する。本サイトはfputc関数(N1570 7.21.7.3)ループを仮定するもののfwrite関数ループなど他の可能性も考えられる。
msvcrtランタイムのコマンドプロンプト出力はLC_CTYPEロケールが"C"でないと文字列をLC_CTYPEロケールに対応するコードページ(整数値)からコマンドプロンプトのコードページへ変換して出力する。
サンプルコードは英語(米国)"OEM"コードページ437と"ANSI"コードページ1252それぞれの文字列"Ää"の出力を試す。TestWithCP関数は指定したコードページ(例えば437)からウィンドウズ準拠のロケール名(".437")を生成してsetlocale(LC_CTYPE,...)してprintfをコールする。printfはこれに従うコードページで出力し、同じコードページの"Ää"は正しく表示して違う場合は文字化けする。CHCPでコマンドプロンプトのコードページを変更しても結果に影響しない。ただし日本語932など字形を持たないコードページは"Aa"で代替表示する。
msvcrtランタイムなのでコードページ変換の実装をソースコードで確認できない。考えられるのはユニコード(UTF-16)文字列経由でウィンドウズAPIのMultiByteToWideChar/WideCharToMultiByte関数を使う。WriteConsoleWithCP関数はそれを試しロケール設定したprintfと等しい出力を得る。
もちろん我々の問題は出力文字列もコマンドプロンプトもコードページ932で変換不要だが、それでもLC_CTYPEロケールが"C"でなければ変換処理が行われる。
プログラム起動時、対話装置を指す標準出力へのC言語標準ライブラリによる出力(C言語標準出力)はバッファリングしない(JTC1/SC22/WG14 N1570 7.21.3/p7)。標準出力へのC++標準ライブラリによる出力(C++標準出力)はC言語標準出力との同期をデフォルトとする(JTC1/SC22/WG21 N4659 30.4.2/p5)。つまり規格はコマンドプロンプトへのC言語/C++標準出力はバッファリングしない事をデフォルトとする。サンプルコードでこれを確認する。putc_by_posix_write関数はPOSIXのwrite関数、putc_by_winapi_writeconsoleはウィンドウズAPIのWriteConsoleA関数で出力する。writeとWriteConsoleAはC言語標準ライブラリに属さないのでバッファリングせず直接出力する。コマンドプロンプト出力はコード順を維持し、C言語/C++標準出力への8ビット符号/16ビット符号文字列出力もバッファリングせず規格通りである事を確認する。
setvbuf(stdout,...)(N1570 7.21.5.6)をアンコメントするとC言語/C++標準出力はバッファリングして、ループ内putc_by_posix_write/putc_by_winapi_writeconsole出力の後にfflush(stdout)で残りをバッファからフラッシュする。さらにsync_with_stdio(false)をアンコメントするとC++標準出力バッファはC言語標準出力バッファから独立し、putc_by_posix_write/putc_by_winapi_writeconsole出力、C言語標準バッファフラッシュの後にフラッシュする。C言語標準出力バッファは8ビット符号文字/16ビット符号文字で共通(8ビット符号文字で保持)だが、C++標準出力バッファはそれぞれ別のバッファを持つ。
LANGなどの環境変数が設定されていない日本語環境でsetlocale(LC_ALL,"")すればLC_CTYPEロケールは"Japanese_Japan.932"となる。出力文字列コードページ(整数値)はこれで932を選択するがそもそも日本語環境デフォルトなので値が変更されるわけではない。重要なのはLC_CTYPEロケールが"C"から"Japanese_Japan.932"に変更される事で、"C"以外となってコードページ変換を行う。日本語環境コマンドプロンプトコードページも932なので二つのコードページは等しく変換無用だがmsvcrtランタイムは冗長無害と見なして変換処理する。これは1バイト文字コードなら正しいがシフトJISなどの2バイト文字コードで問題となる。
コードページ変換はコマンドプロンプトへ書き込む時に実行する。C言語/C++標準出力がバッファリングせず1バイト出力すれば、その1バイトでコードページ変換する。それがシフトJIS第一符号なら変換失敗し、第二符号でたまたま1符号文字(ANK)に一致すれば変換成功する。fputc関数ループはエラーチェック無しでこれを繰り返しANKへの文字化けとなる。fwrite関数は文字列先頭バイトが第一符号で変換失敗するとエラーと見なし全文字列の出力を中止する。だたし文字列先頭に1符号文字を置けば2符号文字が続いても失敗とならず、まるで先頭バイト(または第二仮引数単位要素)と残りバイト(要素)列を別々に出力するかの振る舞いで理由が全く解らない。POSIXのwrite関数はこの問題を持たずバッファリングも無関係でシフトJIS出力に成功する。
setvbuf(stdout,...)でC言語/C++標準出力をバッファリングすれば1バイト出力も後続バイト列と共に書き込まれて、fputc関数ループとfwrite関数もシフトJIS出力に成功する。問題の再現コードにこれを追加すれば全ての出力に成功する。ただしsync_with_stdio(false)した後はC++標準出力バッファがC言語標準バッファから独立して出力順がコード順と一致しない。
libintlとコマンドプロンプト出力を両立させる方法をまとめる。既に述べたが問題はLC_CTYPEロケールとコマンドプロンプトとの関係で、これはlibintlに限定する話ではない。翻訳のロケールはSetThreadLocale関数で設定するものとして必要なら環境変数でオーバーライドするが、ここでの議論に関係しない。setlocaleをコールしないのが最も単純で本サイトはこれを強く勧めるものの、他の理由でコールする場合がありその対処も示す。
GNU gettextドキュメントが推奨するsetlocale(LC_ALL,"")をコールしない。より正確にはLC_CTYPEロケールを"C"に限定する。
setlocale(LC_ALL,"")をコールする場合、より正確にはLC_CTYPEロケールを"C"以外とする場合の第一の方法を示す。setvbuf(stdio,...)で標準出力をバッファリングする。
setlocale(LC_ALL,"")をコールする場合、より正確にはLC_CTYPEロケールを"C"以外とする場合の第二の方法を示す。sync_with_stdio(false)でC++標準入出力をC言語標準入出力と非同期で行う。
ところでC++標準出力でUTF-16からシステムロケールに対応する文字コードへ変換するにはsetlocale(LC_CTYPE,"")するが、その際もsync_with_stdio(false)が必要になる。つまり日本語環境のC++標準出力で8ビット文字列出力にシフトJIS、16ビット文字列出力にUTF-16を用いるには、setlocale(LC_CTYPE,"")とsync_with_stdio(false)をコールすれば良い。これは偶然の結果だろうか。