回答編集履歴

2

LinuxでC/C++のロケール独立性調査した内容を追記

2023/12/20 15:56

投稿

dameo
dameo

スコア943

test CHANGED
@@ -277,3 +277,47 @@
277
277
 
278
278
  http://www17.plala.or.jp/KodamaDeveloped/LetsProgramming/details_how_to_develop_japanese_application_codecvt_libstdcpp_source.html
279
279
 
280
+ ### LinuxでC/C++のロケール独立性調査
281
+
282
+ LinuxでCと同期したままimbueに別のロケールを設定してみたり、同期を切ってみたりしてみた。
283
+
284
+ ```bash
285
+ cat >test_imbue_different_from_c.cpp <<EOF
286
+ #include <iostream>
287
+ using namespace std;
288
+ void test() {
289
+ // ja_JP.SJIS ... シフトJISをこの名前でロケール追加した環境です。
290
+ // ja_JP.EUC-JP ... EUCをこの名前でロケール追加した環境です。
291
+ std::setlocale(LC_CTYPE, "ja_JP.SJIS");
292
+ wcout.imbue(locale("ja_JP.EUC-JP"));
293
+ wcout << L"日本語" << endl;
294
+ }
295
+ int main() {
296
+ test();
297
+ ios_base::sync_with_stdio(false);
298
+ test();
299
+ return 0;
300
+ }
301
+ EOF
302
+ g++ -g -Wall test_imbue_different_from_c.cpp -o test_imbue_different_from_c
303
+ for enc in "cp932" "euc-jp"; do
304
+ echo "[$enc]"
305
+ ./test_imbue_different_from_c | iconv -c -f $enc
306
+ done
307
+ ```
308
+
309
+ #### 結果
310
+ ```bash
311
+ [cp932]
312
+ 日本語
313
+ ニヒワク
314
+ [euc-jp]
315
+ {
316
+ 日本語
317
+ ```
318
+
319
+ #### 考察
320
+ 同期している場合、C側でワイド→マルチバイト文字列変換をしているので、シフトJISで出ていて、imbueは効いていない。
321
+ 同期していない場合、C++だけで処理しているため、ワイド→マルチバイト文字列変換にimbueのロケールが効いている。
322
+
323
+ MINGW64ではimbueに"C"ロケール以外設定できないので、同期していない場合日本語などのエンコーディングは指定できない。すると同期を切ったときにロケールの設定手段がなくなってしまうので、とりあえずC言語のロケール設定を使っているのかもしれない。またWindowsでは_writeがテキストモードで端末に合わせたエンコーディング変換を行っている挙動を確認したので、ロケール未設定("C")のデメリットが大きいと踏んだのかもしれない。独自のCRTを持てないMINGW64では--enable-clocale=genericにせざるをえない。結果C言語のロケール設定に連動することを許容したのではないかと思う。

1

std::ios_base::sync_with_stdioについて追記

2023/12/20 15:50

投稿

dameo
dameo

スコア943

test CHANGED
@@ -195,3 +195,85 @@
195
195
  UCRTの出力調査としては十分だと思うので、次からは最初の回答に書いた``std::ios_base::sync_with_stdio(false);``とcin/wcin周りをVC++やLinuxも合わせて見てみる。
196
196
 
197
197
  これらが終われば、MINGW64/UCRT64での基本的な使い方が分かるはず。
198
+
199
+ ---
200
+
201
+ ## MINGW64での日本語出力について
202
+
203
+ ここからはMINGW64(MSVCRT)メインに話を戻します。
204
+ まずはstdioとの同期を切るだけでwcoutが使える事実に基づき、`std::ios_base::sync_with_stdio`を調査しました。
205
+
206
+ ### std::ios_base::sync_with_stdioについて
207
+
208
+ https://cpprefjp.github.io/reference/ios/ios_base/sync_with_stdio.html
209
+ https://en.cppreference.com/w/cpp/io/ios_base/sync_with_stdio
210
+
211
+ C/C++でバッファを同期するかどうかで、実装方法については特別に規定はないありません。ただし、Linux gccだと以下のような動作になっているようです。
212
+
213
+ - 同期する場合はCの関数を経由してシステムコールから出力される
214
+ - 同期しない場合は直接システムコールから出力される
215
+
216
+ なので同期しない場合ワイド文字のストリームはデフォルトロケール("C")だと出力できなくなります。
217
+ 確認用のスクリプトが以下になります。
218
+
219
+ ```bash
220
+ for sync_flag in "true" "false"; do
221
+ for out in "cout" "wcout"; do
222
+ echo "[${sync_flag}-${out}]"
223
+ prefix=""
224
+ if [ "$out" == "wcout" ]; then
225
+ prefix="L"
226
+ fi
227
+ exefile="test_sync_with_stdio_${sync_flag}_${out}"
228
+ cat >"${exefile}.cpp" <<EOF
229
+ #include<iostream>
230
+ using namespace std;
231
+ int main() {
232
+ ::setlocale(LC_CTYPE, "");
233
+ ios_base::sync_with_stdio(${sync_flag});
234
+ ${out} << ${prefix}"abc日本語" << endl;
235
+ return 0;
236
+ }
237
+ EOF
238
+ g++ -g -Wall ${exefile}.cpp -o ${exefile}
239
+ gdb ./${exefile} <<EOF
240
+ r
241
+ b write
242
+ r
243
+ where
244
+ delete 1
245
+ c
246
+ q
247
+ EOF
248
+ done
249
+ done
250
+ ```
251
+
252
+ ### MINGW64で同じことをしてみる
253
+
254
+ 先のスクリプトをちょっと置換して使用する(システムコールが違うので)
255
+ ```bash
256
+ $ sed -i 's/write/WriteFile/g' test_sync_with_stdio.sh
257
+ ```
258
+
259
+ 結果は以下のとおり。
260
+
261
+ |環境/処理系|std::ios_base::sync_with_stdio|出力|CRT入り口|
262
+ |:--|:--:|:--:|:--:|
263
+ |linux|true|cout|_IO_new_file_overflow(fputc)|
264
+ |linux|true|wcout|__GI_putwc(fputwc)|
265
+ |linux|false|cout|__GI___libc_write(write)|
266
+ |linux|false|wcout|止まらない|
267
+ |mingw64|true|cout|msvcrt!fwrite|
268
+ |mingw64|true|wcout|msvcrt!fputwc|
269
+ |mingw64|false|cout|msvcrt!_write|
270
+ |mingw64|false|wcout|msvcrt!_write|
271
+
272
+ LinuxとMINGW64で比較的似ているようにも見えるが、MINGW64では同期せずwcoutのとき出力されているし、_writeはWriteFileのラッパーではなく、文字コード変換を含む重量級の関数として実装されている。
273
+
274
+ デフォルトのwcoutは"C"ロケール設定なので、同期しない(=ワイド→マルチバイトをC++側でやる)場合もASCII以外がマルチバイト文字列にならないはずだが(LinuxではASCIIすら出力されていない)、MINGW64ではなぜかC言語側のロケール設定を使って変換されている。本来はimbueで設定されたロケールに従い、C言語側と同様の方法で出力されるべきだと思う(ロケール関連は基本実装依存だが)。
275
+
276
+ なお、C言語側のロケール設定を使って変換されるのはMINGW64のgccのconfigで--enable-clocaleにgenericが選ばれたため。Linuxではgnuが選ばれている。
277
+
278
+ http://www17.plala.or.jp/KodamaDeveloped/LetsProgramming/details_how_to_develop_japanese_application_codecvt_libstdcpp_source.html
279
+