実現したいこと
MINGW64で作ったC++プログラムで正しく日本語を扱いたい
前提
- MSYS2-MINGW64使用(※)
- MSVCのRuntimeを使用する
- libstdc++を使用する
- gccを使用する(
現状12.2.0→13.2.0) - MSYS2などの環境に依存せず、作成したバイナリは単品で動作する
- Windows 10以降はmustで、可能ならそれ以前でも正しく動作させたい
- MSYS2環境のbashで動かす場合、環境変数LC_*/LANG/LANGUAGEは未設定とする(LC_CTYPE=ja_JP.UTF-8は可)
- 端末(conhost/mintty/etc...)は何でもいいし、適宜設定は変更するものとするが、原則デフォルトの状態で動作すること
- 日本語Windowsの場合、システムロケールのコードページは932でも65002でもどちらでも動作するものする
- 紐付けられた端末があり、端末への出力が実際にある場合、端末のコードページには追従してもいいし、しなくてもいいが、システムロケールのコードページと端末のコードページのいずれかで入出力できていること
※参考
https://www.msys2.org/docs/environments/
質問
MINGW64で日本語を扱うプログラムをC++で正しく記述する方法を教えてください。
何も考えずに実装すると、Hello, worldレベルで正しく動きません。
発生している問題・エラーメッセージ
例を2つほど挙げます
※ソースコードは次節参照
(1) std::coutに出力するケース
ビルドと実行(システムロケールはコードページ932でmsys2環境(mintty)で実施)
bash
1$ g++ -g -Wall -static hello.cpp -o hello 2$ env | grep LANG 3$ env | grep LC 4LC_CTYPE=ja_JP.UTF-8 5$ ./hello 6縺薙s縺ォ縺。縺ッ 7 8$
msys2環境のminttyの設定はja_JPのUTF-8にしようが、defaultのままにしようが、同じです。
一言で言えば、この状態からcmd /c "dir"したときに日本語が化けないのと同様の、CP932→UTF-8の変換が「どこかで」かかっている文字の化け方をしています。
(2) std::wcoutに出力するケース
bash
1$ g++ -g -Wall -static hellow.cpp -o hellow 2$ ./hellow 3 4$
これは端末以前に何も出力されていないことを示しています。(実際にパイプで繋いでも何も出ていません。)
該当のソースコード
hello.cpp(utf-8で保存)
C++
1#include <iostream> 2int main() { 3 std::cout << "こんにちは" << std::endl; 4 return 0; 5}
hellow.cpp(utf-8で保存)
C++
1#include <iostream> 2int main() { 3 std::wcout << L"こんにちは" << std::endl; 4 return 0; 5}
試したこと
(1) hexdumpで調べる
bash
1$ ./hello | hexdump -C 200000000 e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af 0d |................| 300000010 0a |.| 400000011 5 6$ ./hellow | hexdump -C 7 8$
MSYS2 bashでパイプで出力する場合、./helloはUTF-8で出力されている
MSYS2 bashでパイプで出力する場合、./hellowは何も出力しない
(2) iconvで調べる
bash
1$ ./hello | iconv -f utf-8 -t cp932 2▒▒▒▒ɂ▒▒▒ 3 4$ ./hello | iconv -f utf-8 -t cp932 | hexdump -C 500000000 82 b1 82 f1 82 c9 82 bf 82 cd 0d 0a |............| 60000000c 7 8$
iconvでシフトJISに変換出力してみたが、さらに文字化けするだけだった
hexdumpの結果を見る限り変換は正しく実施されている
(3) catを噛ませてみる
bash
1$ ./hello | cat 2こんにちは 3 4$./hello | cat | hexdump -C 500000000 e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af 0d |................| 600000010 0a |.| 700000011 8 9$ env | egrep -E '(LC_|LANG)' 10LC_CTYPE=ja_JP.UTF-8 11 12$
MSYS2のcatを噛ませると、同じ内容(hexdumpの結果からUTF-8と分かる)でも「なぜか」正しく表示される。
bash/minttyへの出力方法が./helloとcatで異なっていて、その区別から./helloだけCP932→UTF-8変換をかけられていると推測する。
(4) cmdから見てみる
cmd
1C:\>hello 2縺薙s縺ォ縺。縺ッ 3 4C:\>hello | C:\msys64\usr\bin\cat 5こんにちは 6 7C:\>chcp 8現在のコード ページ: 932 9 10C:\>set | findstr LC_ 11 12C:\>set | findstr LANG 13 14C:\>
※conhost.exe上のcmd.exe(minttyなどMSYS2環境ではなく、純粋なWindows環境)
コマンドプロンプトからでも(3)の現象は同様に再現する。また環境変数の影響でもなく、cmdは子プロセスに変換などの細工をしないので、helloは素直にUTF-8で出力していることが確定し、catが単体でUTF-8→CP932の変換をしているということが分かる。
(5) 絵文字を出力させてみる
bash
1$ cat emoji.cpp 2#include <iostream> 3int main() { 4 std::cout << "😊" << std::endl; 5 return 0; 6} 7 8$ g++ -g -Wall -static emoji.cpp -o emoji 9 10$ ./emoji 11・ 12 13$ ./emoji | cat 14😊 15 16$ ./emoji | cat | hexdump -C 1700000000 f0 9f 98 8a 0d 0a |......| 1800000006 19 20$
絵文字を表示可能なことから、シフトJISで表示しようとしていないことが分かる。さらに具体的に言えば、bashやcatがパイプからの読み込み時に変換をかけていないこと、受け取ったminttyも変換していないことが分かる。
(6) リテラルをシフトJISとしてコンパイルさせる
bash
1$ g++ -g -Wall -static -fexec-charset=CP932 hello.cpp -o hello_sjis 2 3$ ./hello_sjis 4こんにちは 5 6$ ./hello_sjis | hexdump -C 700000000 82 b1 82 f1 82 c9 82 bf 82 cd 0d 0a |............| 80000000c 9 10$
シフトJISとして出力されており、正しく表示されている(minttyはロケールCの文字セットUTF-8)。
(7) 依存DLLを調べる
bash
1$ ldd hello 2 ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fffc4950000) 3 KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7fffc4660000) 4 KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7fffc2400000) 5 msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x7fffc45c0000) 6 7$ ldd `which cat` 8 ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fffc4950000) 9 KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7fffc4660000) 10 KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7fffc2400000) 11 msys-intl-8.dll => /usr/bin/msys-intl-8.dll (0x430b30000) 12 msys-2.0.dll => /usr/bin/msys-2.0.dll (0x180040000) 13 msys-iconv-2.dll => /usr/bin/msys-iconv-2.dll (0x5603f0000) 14 15$ ldd `which bash` 16 ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fffc4950000) 17 KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7fffc4660000) 18 KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7fffc2400000) 19 USER32.dll => /c/WINDOWS/System32/USER32.dll (0x7fffc3a60000) 20 win32u.dll => /c/WINDOWS/System32/win32u.dll (0x7fffc2970000) 21 msys-2.0.dll => /usr/bin/msys-2.0.dll (0x180040000) 22 GDI32.dll => /c/WINDOWS/System32/GDI32.dll (0x7fffc32e0000) 23 gdi32full.dll => /c/WINDOWS/System32/gdi32full.dll (0x7fffc20c0000) 24 msvcp_win.dll => /c/WINDOWS/System32/msvcp_win.dll (0x7fffc2360000) 25 ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7fffc2260000) 26 27$
catやbashなどはmsys-2.0.dllをリンクしており、MSYS2ランタイムで動作しているのが分かる。
helloはMINGW64プログラムなのでMSYS2ランタイムで動作していない。
以上から標準入出力の繋がり方が、MSYS2ランタイムを使ったもの同士とそれ以外で異なるのではないかと推測している。
パイプの場合はbashとcmdで違うのではないかと推測している。
(8) wcoutの設定
bash
1$ cat hellow2.cpp 2#include <iostream> 3int main() { 4 std::wcout.imbue(std::locale("")); 5 std::wcout << L"こんにちは" << std::endl; 6 return 0; 7} 8 9$ g++ -g -Wall -static hellow2.cpp -o hellow2 10 11$ ./hellow2.exe 12terminate called after throwing an instance of 'std::runtime_error' 13 what(): locale::facet::_S_create_c_locale name not valid 14 15$
システムロケールへの設定はできない→wcoutは使えない
(https://stackoverflow.com/a/20181564)。
より正確にはwcoutは日本語の出力ができない。libstdc++がgnuモデル以外(今回はgeneralモデル)での変換を実装していないため。
(9) 調べたことまとめ
上記から、MINGW64で正しく日本語を扱うには、
- wcoutを使わず、coutを使う
- msvcrtかWindowsAPIか外部ライブラリなどを使って自分でエンコーディングを(システムロケールのコードページか端末のコードページに)変換する
- リテラルをマルチバイトの固定エンコーディングにする(シフトJISなど)とワイド文字経由のコードページ変換が2度必要になり、手間がかかるため、ワイド文字列のリテラルか、u8プレフィックス付きを使うべき(ただしC++20でchar型での扱いができなくなる)
なのかと思う
ただマルチバイト(cout)にすると、
- 入出力時に変換が必要になり、わざわざマルチバイトにして出力した文字列をWin32側でまたワイド文字列に戻すなどの処理が入りそう
これはそもそもWriteConsoleWがワイド文字列を受け取れるから。
普段使いしてる人の正しい日本語の扱い方を知りたい
補足情報(FW/ツールのバージョンなど)
msys/msys2-runtime 3.4.6-2 → 3.4.10-2
※デバッグ用のシンボル情報が欲しくて最新バージョンに更新してビルドしました(2023/12/18)。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答7件
0
ベストアンサー
さらに回答欄使います。続きですが、一応これで最後のはず。
以上を踏まえた私見
Linuxなどを始めとするUnixライクOSではUTF-8一色だが、WindowsはシステムロケールそのものがもうシフトJISで、それこそ無印の文字列リテラルを入れてVC++でビルドするだけで自動でシフトJISが埋め込まれて増殖する。そして2023年12月19日現在も、システムロケールをUTF-8に変更することがベータ機能であることに変わりがない。標準の仕組みでそれをUTF-8に変換する方法はC言語の標準関数を使用し、ワイド文字を経由してロケールを変更する以外になく、近い将来にシステムロケールが変更されるまでの間、現状のMINGW64の選択は、そこまで悪くないように思う(むしろこんな状況なのにcodecvtを優先度低にしてる方が問題だと思う)。genericではなくgnuを用いているLinuxなどでは、CとC++のlocaleは正しく分離されているものの、基本的には自前のCランタイムを使用して変換されており、標準関数に近いものの(スレッドごとに独立したlocaleを切り替えて使用する)、拡張された関数の使用が認められる。MINGW64でLinuxと同様に実装するなら、gccの同オプションにMSVCRT/UCRT専用の実装が必要となるはず。
なお、Cロケールにcodeconvに独自実装を加えたfacetを指定して新しいロケールを自作する方法もあるが、codecvtがC++17からdeprecatedになってる昨今、そこまでして拡張Cロケールのようなものを作るくらいなら、iconvなどを用いたマルチバイト文字列同士の変換の仕組みを独自に用意した方が分かりやすい気がする。ただLinuxもWindowsもロケールをCとC++で分けているので、そこを問題視してMINGW64でもimbueを使えるようにする可能性はあるのかもしれない。
以上から、MINGW64(MSVCRT)環境で日本語を使用するのはかなり厳しい。
(A)案
安全側に振るなら、Cロケールを使用してC/C++のロケールには触らず、iconvやicuやWindows API(Windows固定になるけど)などを使ったマルチバイト文字列間の自前のエンコーディング変換システムを作るかどこかから持ってくるのが良いと思う。入出力時にはシステムロケールに自前で合わせればいい。変換方法が汎用なら他の環境でも動作する。MSVCRTのmbstowcs/wcstombsでもUTF-8が使えないだけなので、その部分を自作してロケールを切り替える処理を入れれば標準関数だけで記述可能だと思う。
(B)案
安全面を軽視し、標準側に寄せるなら、stdioと同期させず、ワイド文字列で実装するのが良いと思う。ただしシステムロケールがUTF-8になったらUCRT64に移行する必要があるし、(UCRT64でも)ストリームごとにロケールを変えることができないし、そもそも質問の前提を満たしていない。UCRT64に移行する前提のときのみ選べる。また標準関数しか使ってない割に他の環境では動作しない(stdioと同期しないのにCのロケールを使って変換するから)。UCRT64に移行して同期をするならいい案だと思う。
(C)案
現行仕様では正しいものの、作業量も多くて、将来的には足場から崩落が決まっているcodecvtを使う方法もなくはない。C++的にはこれが唯一正しい方法なので。ただし問題も顕在化しており、廃止予定である。変換方法が汎用なら他の環境でも動作する。
各サンプルコード
A案
cpp
1#include <winnls.h> 2#include <iostream> 3#include <vector> 4using namespace std; 5struct myconv { 6 static string convert(const string& s, UINT from, UINT to) { 7 if (from == to) return s; 8 auto len = MultiByteToWideChar(from, 0, s.c_str(), s.length(), nullptr, 0); 9 if (len == 0) return ""; 10 vector<wchar_t> wv(len); 11 len = MultiByteToWideChar(from, 0, s.c_str(), s.length(), wv.data(), wv.size()); 12 if (len == 0) return ""; 13 len = WideCharToMultiByte(to, 0, wv.data(), wv.size(), nullptr, 0, nullptr, nullptr); 14 if (len == 0) return ""; 15 vector<char> bv(len); 16 len = WideCharToMultiByte(to, 0, wv.data(), wv.size(), bv.data(), bv.size(), nullptr, nullptr); 17 if (len == 0) return ""; 18 bv.resize(len); 19 return string(bv.data(), bv.size()); 20 } 21}; 22int main() { 23 auto cp = GetACP(); 24 string line; 25 while (getline(cin, line)) { 26 string uline = myconv::convert(line, cp, 65001); 27 string::size_type pos = string::npos; 28 string s("本"); 29 while ((pos = uline.find(s)) != string::npos) { 30 uline.erase(pos, s.length()); 31 } 32 cout << myconv::convert(uline, 65001, cp) << endl; 33 } 34 return 0; 35}
B案
cpp
1#include <iostream> 2using namespace std; 3int main() { 4 ios_base::sync_with_stdio(false); 5 setlocale(LC_CTYPE, ""); 6 wstring line; 7 while (getline(wcin, line)) { 8 wstring::size_type pos = wstring::npos; 9 wstring s(L"本"); 10 while ((pos = line.find(s)) != wstring::npos) { 11 line.erase(pos, s.length()); 12 } 13 wcout << line << endl; 14 } 15 return 0; 16}
C案
cpp
1#include <iostream> 2// http://www17.plala.or.jp/KodamaDeveloped/LetsProgramming/details_how_to_develop_japanese_application_codecvt_facet_source.html 3#include "TMyCodeCvt.h" 4using namespace std; 5int main() { 6 ios_base::sync_with_stdio(false); 7 auto loc = locale( 8 locale(), 9 new TMyCodeCvt<TMyCodeCvtStateIconv<NMyEncoding::UTF16, NMyEncoding::ShiftJIS>>()); 10 std::wcout.imbue(loc); 11 std::wcin.imbue(loc); 12 wstring line; 13 while (getline(wcin, line)) { 14 wstring::size_type pos = wstring::npos; 15 wstring s(L"本"); 16 while ((pos = line.find(s)) != wstring::npos) { 17 line.erase(pos, s.length()); 18 } 19 wcout << line << endl; 20 } 21 return 0; 22}
サンプルコードはこんな感じで使います。
bash
1$ (echo "日本ホン本本語";echo "ほげ") | iconv -t cp932 | ./example_c.exe 2日ホン語 3ほげ 4 5$
今回解決させて頂いたのはアカウント削除予定だからで、明確な結論が出たわけではないことをお詫びいたします。
投稿2023/12/20 16:27
編集2024/08/28 11:45退会済みユーザー
総合スコア0
0
さらに回答欄使います。続きです。
MINGW64での同期しないC言語ロケールによる日本語変換の挙動確認
先の実験で作成した./test_sync_with_stdio_false_wcout.exeをgdbで実行し、WideCharToMultiByteでブレークさせ、コールスタックを表示したのが以下。
gdb
1(gdb) where 2#0 0x00007ffe8e955e40 in WideCharToMultiByte () from C:\WINDOWS\System32\kernel32.dll 3#1 0x00007ffe392d2dc8 in __wcrtomb_cp (dst=dst@entry=0x5ffc00 "\020", wc=<optimized out>, 4 wc@entry=97 L'a', cp=<optimized out>, mb_max=mb_max@entry=2) 5 at C:/M/B/src/mingw-w64/mingw-w64-crt/misc/wcrtomb.c:35 6#2 0x00007ffe392d2e25 in wcrtomb (dst=<optimized out>, wc=97 L'a', ps=<optimized out>) 7 at C:/M/B/src/mingw-w64/mingw-w64-crt/misc/wcrtomb.c:52 8#3 0x00007ffe3933303a in std::codecvt<wchar_t, char, int>::do_out ( 9 this=0x7ffe39444a20 <(anonymous namespace)::codecvt_w>, __state=@0x7ffe394452e0: 0, 10 __from=0xd77000 L"abc日本語\n(省略)"..., 11 __from_end=0xd7700e L"(省略)"..., 12 __from_next=@0x5ffc18: 0x84001000100010 <error: Cannot access memory at address 0x84001000100010 13>, __to=0x5ffc00 "\020", __to_end=0x5ffc0e "\020", 14 __to_next=@0x5ffc20: 0x84008400840084 <error: Cannot access memory at address 0x84008400840084>) 15 at codecvt_members.cc:65 16#4 0x00007ffe39315620 in std::__codecvt_abstract_base<wchar_t, char, int>::out ( 17 this=0x7ffe39444a20 <(anonymous namespace)::codecvt_w>, __state=@0x7ffe394452e0: 0, 18 __from=0xd77000 L"abc日本語\n(省略)"..., 19 __from_end=0xd7700e L"(省略)"..., 20 __from_next=@0x5ffc18: 0x84001000100010 <error: Cannot access memory at address 0x84001000100010 21>, __to=0x5ffc00 "\020", __to_end=0x5ffc0e "\020", 22 __to_next=@0x5ffc20: 0x84008400840084 <error: Cannot access memory at address 0x84008400840084>) 23 at C:/msys64/home/user/mingw/src/MINGW-packages/mingw-w64-gcc/src/build-MINGW64/x86_64-w64-mingw32 24/libstdc++-v3/include/bits/codecvt.h:124 25#5 0x00007ffe393a3991 in std::basic_filebuf<wchar_t, std::char_traits<wchar_t> >::_M_convert_to_ext 26ernal (this=0x7ffe39445280 <__gnu_internal::buf_wcout>, 27 __ibuf=0xd77000 L"abc日本語\n(省略)"..., 28 __ilen=7) 29 at C:/msys64/home/user/mingw/src/MINGW-packages/mingw-w64-gcc/src/build-MINGW64/x86_64-w64-ming 30w32/libstdc++-v3/include/bits/fstream.tcc:626 31#6 0x00007ffe393a50e6 in std::basic_filebuf<wchar_t, std::char_traits<wchar_t> >::overflow ( 32 this=0x7ffe39445280 <__gnu_internal::buf_wcout>, __c=65535) 33 at C:/msys64/home/user/mingw/src/MINGW-packages/mingw-w64-gcc/src/build-MINGW64/x86_64-w64-ming 34w32/libstdc++-v3/include/bits/fstream.tcc:568 35#7 0x00007ffe393a418a in std::basic_filebuf<wchar_t, std::char_traits<wchar_t> >::sync ( 36 this=0x7ffe39445280 <__gnu_internal::buf_wcout>) 37 at C:/msys64/home/user/mingw/src/MINGW-packages/mingw-w64-gcc/src/build-MINGW64/x86_64-w64-ming 38w32/libstdc++-v3/include/bits/fstream.tcc:1016 39#8 0x00007ffe393bc993 in std::basic_streambuf<wchar_t, std::char_traits<wchar_t> >::pubsync ( 40 this=0x7ffe39445280 <__gnu_internal::buf_wcout>) 41 at C:/msys64/home/user/mingw/src/MINGW-packages/mingw-w64-gcc/src/build-MINGW64/x86_64-w64-ming 42w32/libstdc++-v3/include/streambuf:278 43#9 0x00007ffe393adb2d in std::basic_ostream<wchar_t, std::char_traits<wchar_t> >::flush ( 44 this=0x7ffe394464a0 <std::wcout>) 45 at C:/msys64/home/user/mingw/src/MINGW-packages/mingw-w64-gcc/src/build-MINGW64/x86_64-w64-ming 46w32/libstdc++-v3/include/bits/ostream.tcc:237 47#10 0x00007ffe39432518 in std::flush<wchar_t, std::char_traits<wchar_t> > (__os=...) 48 at C:/msys64/home/user/mingw/src/MINGW-packages/mingw-w64-gcc/src/build-MINGW64/x86_64-w64-ming 49w32/libstdc++-v3/include/ostream:758 50#11 0x00007ffe39430318 in std::endl<wchar_t, std::char_traits<wchar_t> > (__os=...) 51 at C:/msys64/home/user/mingw/src/MINGW-packages/mingw-w64-gcc/src/build-MINGW64/x86_64-w64-ming 52w32/libstdc++-v3/include/ostream:736 53#12 0x00007ffe393afabd in std::basic_ostream<wchar_t, std::char_traits<wchar_t> >::operator<< ( 54 this=0x7ffe394464a0 <std::wcout>, 55 __pf=0x7ff6931d14c8 <std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& std::endl<wchar_t, 56 std::char_traits<wchar_t> >(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >&)>) 57 at C:/msys64/home/user/mingw/src/MINGW-packages/mingw-w64-gcc/src/build-MINGW64/x86_64-w64-ming 58w32/libstdc++-v3/include/ostream:115 59#13 0x00007ff6931d14a6 in main () at test_sync_with_stdio_false_wcout.cpp:6 60(gdb)
std::codecvt<wchar_t, char, int>::do_out()
から直接wcrtomb()
が呼ばれていることが分かる。
MINGW64では普通にパッケージインストールしてもC++標準ライブラリにシンボル情報はついていないので上のような表示にはならないが、今回はソースからビルドし、デバッグ情報を付けたもの(PKGBUILDの'!strip' 'debug')
のコメントを外してビルドしたlibstdc++-6.dll)を使って動かしたので、表示されている。
想定どおりの挙動であることが確認できた。
なお、このsync_with_stdio(false)
時の挙動はMINGW64だけでなく、UCRT64でも同様で、std::locale
が"C"以外使用できないのも同様。
sync_with_stdio(false)
はCとの同期を外して速度アップ効果を期待する使用が普通なので(他にもimbueによるストリームごとにロケールを変えたりなどが可能)、ただしcodecvtはC++17でdeprecateになっており(不要という意味ではなく、仕様に欠陥があり代替品が必要という意味。ただし非UTFエンコーディングの使用頻度の減少から優先度低とか)、積極的な使用は控えるべきではある。
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0618r0.html
In published usage surveys like BuiltWith, use of formerly popular non-UTF encodings like Shift-JIS and Big5 is plummeting compared to UTF encodings.
std::wcinについて
一応確認だけ。wcoutについてはios_base::sync_with_stdio(false)が前提となるので、それがcinで問題ないのかを確認する。
bash
1for w in "" "w"; do 2 fname="test_cin_locale_$w" 3 cat >${fname}.cpp <<EOF 4#include <iostream> 5using namespace std; 6int main() { 7 ios_base::sync_with_stdio(false); 8 setlocale(LC_CTYPE, ""); 9 ${w}string s; 10 ${w}cin >> s; 11 ${w}cout << s << endl; 12 return 0; 13} 14EOF 15 g++ -g -Wall $fname.cpp -o $fname 16 echo "abc日本語" | iconv -t cp932 | ./$fname 17done
→OK
※このスクリプトでは端末操作になってないが、生成されたバイナリをパイプ/リダイレクトを使用せず手打ちで確認しても同じ結果となった。
なお、UCRTではios_base::sync_with_stdio(false)がなくても動作する。MSVC++での確認も軽くしてみたが、Linuxと似たような挙動となっていた。紙面の都合上詳細は省略。
MSVCRTのfputwcと_writeの問題について
https://teratail.com/questions/fqia83ktejutp3
mingw64では文字出力時、最終的にCのランタイムにMSVCRTを使用するが_writeにはリンク先にあるように、".932"ロケールではテキストストリームに2バイト文字の1バイト目のみを出力しようとするとENOSPCを返す問題がある。これに付随してfputwcは常に失敗するため、wcoutも常に失敗してしまっていた。_writeは何を出力するにしてもWindowsのCRTを使う限り通るので、coutでも失敗する可能性があり、他のロケールでどうなるかも分からないので、以下のスクリプトで調査した。
bash
1for loc in "C" ".932" ".UTF-8"; do 2 for sync_flag in "true" "false"; do 3 for out in "cout" "wcout"; do 4 for alpcnt in 0 4095 4096 4097; do 5 echo "=============================================================" 6 echo "[${loc}_${sync_flag}_${out}_${alpcnt}]" 7 prefix="" 8 if [ "$out" == "wcout" ]; then 9 prefix="L" 10 fi 11 exefile="test_sync_with_stdio_no_abc_${loc}_${sync_flag}_${out}_${alpcnt}" 12 cat >"${exefile}.cpp" <<EOF 13#include<iostream> 14using namespace std; 15int main() { 16 if (::setlocale(LC_CTYPE, "${loc}") == nullptr) { 17 perror("${exefile}: setlocale failed"); 18 return 1; 19 } 20 ios_base::sync_with_stdio(${sync_flag}); 21 for (size_t i = 0; i < ${alpcnt}; ++i) ${out} << ${prefix}"A"; 22 ${out} << ${prefix}"本"; 23 ${out}.flush(); 24 return 0; 25} 26EOF 27 g++ -g -Wall ${exefile}.cpp -o ${exefile} 28 gdb ./${exefile} <<EOF 29r 30b main 31r 32n 33n 34n 35b _write 36c 37where 38delete 2 39c 40q 41EOF 42 echo 43 done 44 done 45 done 46done
スクリプトを動かすと分かるが、MINGW64(MSVCRT)環境で調べたロケールのうち"C"と".932"しか使えなかった(上にはないがEUC-JPなども使用できる)。".UTF-8"は(".65001"も)setlocaleに失敗する。つまり、coutを使う場合".UTF-8"が使えないということで、リテラルをUTF-8にする限り、マルチバイト文字列は何にも変換できず、"C"ロケールを使うしかない。そして"C"ロケールでは_writeの問題は発生していないので、無関係となる。端末をなんとかUTF-8にしてMSYS2環境などに閉じた生活をしていれば、日本語を使用しても問題ないだろう。ただし、MSYS環境外のWindowsアプリと相互作用(パイプやリダイレクト)する場合は、システムロケールに依存したロケールでやり取りすることになるため、システムロケールをUTF-8にしない限り正しく動作しない。
もしロケール".932"(通常システムロケールを表す""でも同じ)を使用するのであれば、リテラルがUTF-8になるので今度はマルチバイト文字列リテラルが使えなくなる。コンパイルオプションでマルチバイト文字列リテラルを932に変更することも出来るが、元々UTF-8環境のあるMINGW64でわざわざ932を使うのも時代錯誤感がある。なので選択肢としてはワイド文字を使用することになる。今回調べた範囲ではstdioと同期せずにwcoutを使うことで、不都合は見つかっていない。ただし、たまたま動いてるだけで_write()の使われ方次第ですぐ動かなくなる可能性も高い。また将来システムロケールが"UTF-8"になった場合、(setlocaleに失敗するため)そのまま動かなくなる。ただしUCRT64に移行すれば、同じコードでそのまま両方のロケールに対応可能だろう。
投稿2023/12/20 16:21
退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
さらに回答欄使います。
UCRTを使用する場合の出力処理解析
例外的な現象を原因を調査してみた。使用したのはMSYS2/UCRT64環境。
bash
1cat >hoge.cpp <<EOF 2#include <iostream> 3int main() { 4 std::setlocale(LC_CTYPE, DESIRED_LOCALE); 5 std::cout << "日本語" << std::endl; 6 std::wcout << L"日本語" << std::endl; 7 return 0; 8} 9EOF 10g++ -g -DDESIRED_LOCALE=\".UTF-8\" hoge.cpp -o hoge 11./hoge >hoge_utf8_redirected.txt 12gdb ./hoge <<EOF 13run 14break main 15run 16next 17break WideCharToMultiByte 18break MultiByteToWideChar 19continue 20where 21info registers 22continue 23continue 24continue 25continue 26where 27info registers 28continue 29continue 30continue 31continue 32quit 33EOF 34g++ -g -DDESIRED_LOCALE=\"\" hoge.cpp -o hoge 35./hoge >hoge_empty_redirected.txt 36gdb ./hoge <<EOF 37run 38break main 39run 40next 41break WideCharToMultiByte 42break MultiByteToWideChar 43continue 44where 45info registers 46continue 47where 48info registers 49continue 50continue 51continue 52continue 53continue 54continue 55continue 56continue 57continue 58continue 59continue 60where 61info registers 62continue 63where 64info registers 65continue 66where 67info registers 68continue 69continue 70continue 71continue 72continue 73continue 74continue 75continue 76continue 77continue 78quit 79EOF 80g++ -g -DDESIRED_LOCALE=\"C\" hoge.cpp -o hoge 81./hoge >hoge_c_redirected.txt 82gdb ./hoge <<EOF 83run 84break main 85run 86next 87break WideCharToMultiByte 88break MultiByteToWideChar 89continue 90quit 91EOF
リダイレクト用
bash
1cat >hoge.cpp <<EOF 2#include <iostream> 3int main() { 4 std::setlocale(LC_CTYPE, DESIRED_LOCALE); 5 std::cout << "日本語" << std::endl; 6 std::wcout << L"日本語" << std::endl; 7 return 0; 8} 9EOF 10g++ -g -DDESIRED_LOCALE=\".UTF-8\" hoge.cpp -o hoge 11./hoge 12gdb ./hoge <<EOF 13run 14break main 15run 16next 17break WideCharToMultiByte 18break MultiByteToWideChar 19continue 20quit 21EOF 22g++ -g -DDESIRED_LOCALE=\"\" hoge.cpp -o hoge 23./hoge 24gdb ./hoge <<EOF 25run 26break main 27run 28next 29break WideCharToMultiByte 30break MultiByteToWideChar 31continue 32where 33info registers 34continue 35continue 36continue 37continue 38quit 39EOF 40g++ -g -DDESIRED_LOCALE=\"C\" hoge.cpp -o hoge 41./hoge 42gdb ./hoge <<EOF 43run 44break main 45run 46next 47break WideCharToMultiByte 48break MultiByteToWideChar 49continue 50quit 51EOF
ロケールを設定してcoutとwcoutに文字列を出力するだけのプログラムをデバッガで実行し、WideCharToMultiByte/MultiByteToWideCharで停止して調べるというだけのもの。
結果
端末コードページ | 処理系 | 出力先 | ロケール(C) | リテラル(ソースUTF8) | ucrtbase入り口 | Wide→MB | MB→Wide | Wide→MB | 出力先 | 結果 |
---|---|---|---|---|---|---|---|---|---|---|
932 | UCRT64 | cout | ".UTF8" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | →932 | 端末 | "日本語" |
932 | UCRT64 | wcout | ".UTF8" | L"日本語" | ucrtbase!fputwc | ✕ | ✕ | →932 | 端末 | "日本語" |
932 | UCRT64 | cout | "" | "日本語" | ucrtbase!fwrite | ✕ | 932→ | →932 | 端末 | 化けた |
932 | UCRT64 | wcout | "" | L"日本語" | ucrtbase!fputwc | →932 | 932→ | →932 | 端末 | "日本語" |
932 | UCRT64 | cout | "C" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | ✕ | 端末 | "譌・譛ャ隱" |
932 | UCRT64 | wcout | "C" | L"日本語" | ucrtbase!fputwc | ✕ | ✕ | ✕ | 端末 | "" |
65001 | UCRT64 | cout | ".UTF8" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | →65001 | 端末 | "日本語" |
65001 | UCRT64 | wcout | ".UTF8" | L"日本語" | ucrtbase!fputwc | ✕ | ✕ | →65001 | 端末 | "日本語" |
65001 | UCRT64 | cout | "" | "日本語" | ucrtbase!fwrite | ✕ | 932→ | →65001 | 端末 | 化けた |
65001 | UCRT64 | wcout | "" | L"日本語" | ucrtbase!fputwc | →932 | 932→ | →65001 | 端末 | "日本語" |
65001 | UCRT64 | cout | "C" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | ✕ | 端末 | "日本語" |
65001 | UCRT64 | wcout | "C" | L"日本語" | ucrtbase!fputwc | ✕ | ✕ | ✕ | 端末 | "" |
932 | UCRT64 | cout | ".UTF8" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | ✕ | リダイレクト | "譌・譛ャ隱" |
932 | UCRT64 | wcout | ".UTF8" | L"日本語" | ucrtbase!fputwc | ✕ | ✕ | ✕ | リダイレクト | "譌・譛ャ隱" |
932 | UCRT64 | cout | "" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | ✕ | リダイレクト | "譌・譛ャ隱" |
932 | UCRT64 | wcout | "" | L"日本語" | ucrtbase!fputwc | →932 | ✕ | ✕ | リダイレクト | "日本語" |
932 | UCRT64 | cout | "C" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | ✕ | リダイレクト | "譌・譛ャ隱" |
932 | UCRT64 | wcout | "C" | L"日本語" | ucrtbase!fputwc | ✕ | ✕ | ✕ | リダイレクト | "" |
65001 | UCRT64 | cout | ".UTF8" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | ✕ | リダイレクト | "日本語" |
65001 | UCRT64 | wcout | ".UTF8" | L"日本語" | ucrtbase!fputwc | ✕ | ✕ | ✕ | リダイレクト | "日本語" |
65001 | UCRT64 | cout | "" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | ✕ | リダイレクト | "日本語" |
65001 | UCRT64 | wcout | "" | L"日本語" | ucrtbase!fputwc | →932 | ✕ | ✕ | リダイレクト | "▒▒▒{▒▒" |
65001 | UCRT64 | cout | "C" | "日本語" | ucrtbase!fwrite | ✕ | ✕ | ✕ | リダイレクト | "日本語" |
65001 | UCRT64 | wcout | "C" | L"日本語" | ucrtbase!fputwc | ✕ | ✕ | ✕ | リダイレクト | "" |
考察
上記からucrtbase.dllを使用する場合、恐らく
- マルチバイト&端末の場合、ロケールに従ってワイド文字列にしてから端末コードページに変換
- マルチバイト&リダイレクトの場合、そのまま出力
- ワイド&端末の場合、ロケールに従ってマルチバイトにした後、ロケールに従ってワイド文字列に戻してから端末コードページに変換
- マルチバイト&リダイレクトの場合、ロケールに従ってマルチバイトにした後出力
- ただしUTF-8<=>ワイド文字列の変換にはAPIを使用しない
- コンソール系APIは使用していないため、ワイド文字列で端末出力はしていない
例外となる状況の說明が出来た。しかしワイド文字列の出力処理がどう見ても冗長なのが不思議。
UCRTの出力調査としては十分だと思うので、次からは最初の回答に書いたstd::ios_base::sync_with_stdio(false);
とcin/wcin周りをVC++やLinuxも合わせて見てみる。
これらが終われば、MINGW64/UCRT64での基本的な使い方が分かるはず。
MINGW64での日本語出力について
ここからはMINGW64(MSVCRT)メインに話を戻します。
まずはstdioとの同期を切るだけでwcoutが使える事実に基づき、std::ios_base::sync_with_stdio
を調査しました。
std::ios_base::sync_with_stdioについて
https://cpprefjp.github.io/reference/ios/ios_base/sync_with_stdio.html
https://en.cppreference.com/w/cpp/io/ios_base/sync_with_stdio
C/C++でバッファを同期するかどうかで、実装方法については特別に規定はないありません。ただし、Linux gccだと以下のような動作になっているようです。
- 同期する場合はCの関数を経由してシステムコールから出力される
- 同期しない場合は直接システムコールから出力される
なので同期しない場合ワイド文字のストリームはデフォルトロケール("C")だと出力できなくなります。
確認用のスクリプトが以下になります。
bash
1for sync_flag in "true" "false"; do 2 for out in "cout" "wcout"; do 3 echo "[${sync_flag}-${out}]" 4 prefix="" 5 if [ "$out" == "wcout" ]; then 6 prefix="L" 7 fi 8 exefile="test_sync_with_stdio_${sync_flag}_${out}" 9 cat >"${exefile}.cpp" <<EOF 10#include<iostream> 11using namespace std; 12int main() { 13 ::setlocale(LC_CTYPE, ""); 14 ios_base::sync_with_stdio(${sync_flag}); 15 ${out} << ${prefix}"abc日本語" << endl; 16 return 0; 17} 18EOF 19 g++ -g -Wall ${exefile}.cpp -o ${exefile} 20 gdb ./${exefile} <<EOF 21r 22b write 23r 24where 25delete 1 26c 27q 28EOF 29 done 30done
MINGW64で同じことをしてみる
先のスクリプトをちょっと置換して使用する(システムコールが違うので)
bash
1$ sed -i 's/write/WriteFile/g' test_sync_with_stdio.sh
結果は以下のとおり。
環境/処理系 | std::ios_base::sync_with_stdio | 出力 | CRT入り口 |
---|---|---|---|
linux | true | cout | _IO_new_file_overflow(fputc) |
linux | true | wcout | __GI_putwc(fputwc) |
linux | false | cout | __GI___libc_write(write) |
linux | false | wcout | 止まらない |
mingw64 | true | cout | msvcrt!fwrite |
mingw64 | true | wcout | msvcrt!fputwc |
mingw64 | false | cout | msvcrt!_write |
mingw64 | false | wcout | msvcrt!_write |
LinuxとMINGW64で比較的似ているようにも見えるが、MINGW64では同期せずwcoutのとき出力されているし、_writeはWriteFileのラッパーではなく、文字コード変換を含む重量級の関数として実装されている。
デフォルトのwcoutは"C"ロケール設定なので、同期しない(=ワイド→マルチバイトをC++側でやる)場合もASCII以外がマルチバイト文字列にならないはずだが(LinuxではASCIIすら出力されていない)、MINGW64ではなぜかC言語側のロケール設定を使って変換されている。本来はimbueで設定されたロケールに従い、C言語側と同様の方法で出力されるべきだと思う(ロケール関連は基本実装依存だが)。
なお、C言語側のロケール設定を使って変換されるのはMINGW64のgccのconfigで--enable-clocaleにgenericが選ばれたため。Linuxではgnuが選ばれている。
LinuxでC/C++のロケール独立性調査
LinuxでCと同期したままimbueに別のロケールを設定してみたり、同期を切ってみたりしてみた。
bash
1cat >test_imbue_different_from_c.cpp <<EOF 2#include <iostream> 3using namespace std; 4void test() { 5 // ja_JP.SJIS ... シフトJISをこの名前でロケール追加した環境です。 6 // ja_JP.EUC-JP ... EUCをこの名前でロケール追加した環境です。 7 std::setlocale(LC_CTYPE, "ja_JP.SJIS"); 8 wcout.imbue(locale("ja_JP.EUC-JP")); 9 wcout << L"日本語" << endl; 10} 11int main() { 12 test(); 13 ios_base::sync_with_stdio(false); 14 test(); 15 return 0; 16} 17EOF 18g++ -g -Wall test_imbue_different_from_c.cpp -o test_imbue_different_from_c 19for enc in "cp932" "euc-jp"; do 20 echo "[$enc]" 21 ./test_imbue_different_from_c | iconv -c -f $enc 22done
結果
bash
1[cp932] 2日本語 3ニヒワク 4[euc-jp] 5{ 6日本語
考察
同期している場合、C側でワイド→マルチバイト文字列変換をしているので、シフトJISで出ていて、imbueは効いていない。
同期していない場合、C++だけで処理しているため、ワイド→マルチバイト文字列変換にimbueのロケールが効いている。
MINGW64ではimbueに"C"ロケール以外設定できないので、同期していない場合日本語などのエンコーディングは指定できない。すると同期を切ったときにロケールの設定手段がなくなってしまうので、とりあえずC言語のロケール設定を使っているのかもしれない。またWindowsでは_writeがテキストモードで端末に合わせたエンコーディング変換を行っている挙動を確認したので、ロケール未設定("C")のデメリットが大きいと踏んだのかもしれない。独自のCRTを持てないMINGW64では--enable-clocale=genericにせざるをえない。結果C言語のロケール設定に連動することを許容したのではないかと思う。
投稿2023/12/15 10:02
編集2023/12/20 15:56退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
回答欄も足りなくなったので、2個目使います。
システムロケールをUTF-8に変えて先の回答の実験を実施してみました。
システムロケールUTF-8での結果
端末コードページ | 処理系 | 出力先 | ロケール(C) | リテラル(ソースUTF8) | 結果 |
---|---|---|---|---|---|
932 | MINGW64 | cout | "" | "こんにちは世界!" | |
932 | MINGW64 | cout | "C" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | MINGW64 | cout | ".932" | "こんにちは世界!" | |
932 | MINGW64 | cout | ".UTF-8" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | MINGW64 | cout | "" | "😊" | |
932 | MINGW64 | cout | "C" | "😊" | ・ |
932 | MINGW64 | cout | ".932" | "😊" | |
932 | MINGW64 | cout | ".UTF-8" | "😊" | ・ |
932 | MINGW64 | wcout | "" | L"こんにちは世界!" | |
932 | MINGW64 | wcout | "C" | L"こんにちは世界!" | |
932 | MINGW64 | wcout | ".932" | L"こんにちは世界!" | |
932 | MINGW64 | wcout | ".UTF-8" | L"こんにちは世界!" | |
932 | MINGW64 | wcout | "" | L"😊" | |
932 | MINGW64 | wcout | "C" | L"😊" | |
932 | MINGW64 | wcout | ".932" | L"😊" | |
932 | MINGW64 | wcout | ".UTF-8" | L"😊" | |
65001 | MINGW64 | cout | "" | "こんにちは世界!" | |
65001 | MINGW64 | cout | "C" | "こんにちは世界!" | こんにちは世界! |
65001 | MINGW64 | cout | ".932" | "こんにちは世界!" | |
65001 | MINGW64 | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
65001 | MINGW64 | cout | "" | "😊" | |
65001 | MINGW64 | cout | "C" | "😊" | 😊 |
65001 | MINGW64 | cout | ".932" | "😊" | |
65001 | MINGW64 | cout | ".UTF-8" | "😊" | 😊 |
65001 | MINGW64 | wcout | "" | L"こんにちは世界!" | |
65001 | MINGW64 | wcout | "C" | L"こんにちは世界!" | |
65001 | MINGW64 | wcout | ".932" | L"こんにちは世界!" | |
65001 | MINGW64 | wcout | ".UTF-8" | L"こんにちは世界!" | |
65001 | MINGW64 | wcout | "" | L"😊" | |
65001 | MINGW64 | wcout | "C" | L"😊" | |
65001 | MINGW64 | wcout | ".932" | L"😊" | |
65001 | MINGW64 | wcout | ".UTF-8" | L"😊" | |
932 | UCRT64 | cout | "" | "こんにちは世界!" | こんにちは世界! |
932 | UCRT64 | cout | "C" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | UCRT64 | cout | ".932" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | UCRT64 | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
932 | UCRT64 | cout | "" | "😊" | ?? |
932 | UCRT64 | cout | "C" | "😊" | ・ |
932 | UCRT64 | cout | ".932" | "😊" | ・ |
932 | UCRT64 | cout | ".UTF-8" | "😊" | ?? |
932 | UCRT64 | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
932 | UCRT64 | wcout | "C" | L"こんにちは世界!" | |
932 | UCRT64 | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
932 | UCRT64 | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
932 | UCRT64 | wcout | "" | L"😊" | |
932 | UCRT64 | wcout | "C" | L"😊" | |
932 | UCRT64 | wcout | ".932" | L"😊" | |
932 | UCRT64 | wcout | ".UTF-8" | L"😊" | |
65001 | UCRT64 | cout | "" | "こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | cout | "C" | "こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | cout | ".932" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
65001 | UCRT64 | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | cout | "" | "😊" | 😊 |
65001 | UCRT64 | cout | "C" | "😊" | 😊 |
65001 | UCRT64 | cout | ".932" | "😊" | ・ |
65001 | UCRT64 | cout | ".UTF-8" | "😊" | 😊 |
65001 | UCRT64 | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | wcout | "C" | L"こんにちは世界!" | |
65001 | UCRT64 | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | wcout | "" | L"😊" | |
65001 | UCRT64 | wcout | "C" | L"😊" | |
65001 | UCRT64 | wcout | ".932" | L"😊" | |
65001 | UCRT64 | wcout | ".UTF-8" | L"😊" | |
932 | MSVC++ | cout | "" | "こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | cout | "C" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・- |
932 | MSVC++ | cout | ".932" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | MSVC++ | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | cout | "" | "😊" | ?? |
932 | MSVC++ | cout | "C" | "😊" | ・ |
932 | MSVC++ | cout | ".932" | "😊" | ・ |
932 | MSVC++ | cout | ".UTF-8" | "😊" | ?? |
932 | MSVC++ | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | "C" | L"こんにちは世界!" | |
932 | MSVC++ | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | "" | L"😊" | |
932 | MSVC++ | wcout | "C" | L"😊" | |
932 | MSVC++ | wcout | ".932" | L"😊" | |
932 | MSVC++ | wcout | ".UTF-8" | L"😊" | |
65001 | MSVC++ | cout | "" | "こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | cout | "C" | "こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | cout | ".932" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
65001 | MSVC++ | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | cout | "" | "😊" | 😊 |
65001 | MSVC++ | cout | "C" | "😊" | 😊 |
65001 | MSVC++ | cout | ".932" | "😊" | ・ |
65001 | MSVC++ | cout | ".UTF-8" | "😊" | 😊 |
65001 | MSVC++ | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | "C" | L"こんにちは世界!" | |
65001 | MSVC++ | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | "" | L"😊" | |
65001 | MSVC++ | wcout | "C" | L"😊" | |
65001 | MSVC++ | wcout | ".932" | L"😊" | |
65001 | MSVC++ | wcout | ".UTF-8" | L"😊" |
システムロケールCP932で作成したバイナリを、システムロケールUTF-8で実行した結果
端末コードページ | 処理系 | 出力先 | ロケール(C) | リテラル(ソースUTF8) | 結果 |
---|---|---|---|---|---|
932 | MSVC++ | cout | "" | "こんにちは世界!" | |
932 | MSVC++ | cout | "C" | "こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | cout | ".932" | "こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | cout | ".UTF-8" | "こんにちは世界!" | |
932 | MSVC++ | cout | "" | "😊" | ?? |
932 | MSVC++ | cout | "C" | "😊" | ?? |
932 | MSVC++ | cout | ".932" | "😊" | ?? |
932 | MSVC++ | cout | ".UTF-8" | "😊" | ?? |
932 | MSVC++ | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | "C" | L"こんにちは世界!" | |
932 | MSVC++ | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | "" | L"😊" | |
932 | MSVC++ | wcout | "C" | L"😊" | |
932 | MSVC++ | wcout | ".932" | L"😊" | |
932 | MSVC++ | wcout | ".UTF-8" | L"😊" | |
65001 | MSVC++ | cout | "" | "こんにちは世界!" | |
65001 | MSVC++ | cout | "C" | "こんにちは世界!" | ɂ͐EI |
65001 | MSVC++ | cout | ".932" | "こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | cout | ".UTF-8" | "こんにちは世界!" | |
65001 | MSVC++ | cout | "" | "😊" | ?? |
65001 | MSVC++ | cout | "C" | "😊" | ?? |
65001 | MSVC++ | cout | ".932" | "😊" | ?? |
65001 | MSVC++ | cout | ".UTF-8" | "😊" | ?? |
65001 | MSVC++ | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | "C" | L"こんにちは世界!" | |
65001 | MSVC++ | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | "" | L"😊" | |
65001 | MSVC++ | wcout | "C" | L"😊" | |
65001 | MSVC++ | wcout | ".932" | L"😊" | |
65001 | MSVC++ | wcout | ".UTF-8" | L"😊" |
システムロケールUTF-8で作成したバイナリを、システムロケールCP932で実行した結果
端末コードページ | 処理系 | 出力先 | ロケール(C) | リテラル(ソースUTF8) | 結果 |
---|---|---|---|---|---|
932 | MSVC++ | cout | "" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | MSVC++ | cout | "C" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・- |
932 | MSVC++ | cout | ".932" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | MSVC++ | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | cout | "" | "😊" | ・ |
932 | MSVC++ | cout | "C" | "😊" | ・ |
932 | MSVC++ | cout | ".932" | "😊" | ・ |
932 | MSVC++ | cout | ".UTF-8" | "😊" | ?? |
932 | MSVC++ | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | "C" | L"こんにちは世界!" | |
932 | MSVC++ | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | "" | L"😊" | |
932 | MSVC++ | wcout | "C" | L"😊" | |
932 | MSVC++ | wcout | ".932" | L"😊" | |
932 | MSVC++ | wcout | ".UTF-8" | L"😊" | |
65001 | MSVC++ | cout | "" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
65001 | MSVC++ | cout | "C" | "こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | cout | ".932" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
65001 | MSVC++ | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | cout | "" | "😊" | ・ |
65001 | MSVC++ | cout | "C" | "😊" | 😊 |
65001 | MSVC++ | cout | ".932" | "😊" | ・ |
65001 | MSVC++ | cout | ".UTF-8" | "😊" | 😊 |
65001 | MSVC++ | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | "C" | L"こんにちは世界!" | |
65001 | MSVC++ | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | "" | L"😊" | |
65001 | MSVC++ | wcout | "C" | L"😊" | |
65001 | MSVC++ | wcout | ".932" | L"😊" | |
65001 | MSVC++ | wcout | ".UTF-8" | L"😊" |
考察
メモ:
- Cロケールは原則変換しないロケールだからなのか、ワイド文字が出力できない可能性がある
- 恐らくMINGW64/UCRT64ともにUTF-8のままリテラルがバイナリに格納されている(Lプレフィックス除く)
- 恐らくVC++はビルド時のシステムロケールに依存したリテラルがバイナリに格納されている(u8, Lプレフィックス除く)
- 恐らくVC++生成バイナリはマルチバイト文字列がロケール設定に従っており、端末などのコードページと一致すると仮定し、変換しない
- 恐らくVC++生成バイナリは
ロケール設定がUTF-8のとき、端末にワイド文字列として出力する(ワイド用のAPIを使用する)端末のコードページに変換して出力する 恐らくVC++生成バイナリはワイド文字列のとき、端末にワイド文字列として出力する(ワイド用のAPIを使用する)- 恐らくUCRT64生成バイナリはマルチバイト文字列がロケール設定に従っており、端末などのコードページと一致すると仮定し、変換しない
- 恐らくUCRT64生成バイナリは
ロケール設定がUTF-8のとき、端末にワイド文字列として出力する(ワイド用のAPIを使用する)端末のコードページに変換して出力する 恐らくUCRT64生成バイナリはワイド文字列のとき、端末にワイド文字列として出力する(ワイド用のAPIを使用する)- 恐らくMINGW64生成バイナリはマルチバイト文字列がロケール設定に従っており、端末などのコードページと一致すると仮定し、変換しない
- 恐らくMINGW64生成バイナリはワイド文字列を(そのままでは)出力出来ない
- 一応ちゃんと書いておくとUTF-8ロケールを常に指定した場合、システムロケールに依存した作りをしたアプリケーションとパイプなどでやり取りできない→NG
- 絵文字を含む何かの範囲がワイド文字列で出力できない場合が多く、原因の推測が出来ていない(→これは諦める)
- システムロケール932でビルドしてUTF-8で実行したケースの端末CP65001、MSVC++、cout、".932"、"こんにちは世界!"が、「こんにちは世界!」なのは上のいずれの法則にも従っておらず例外的。法則通りなら932のまま変換もされずにマルチバイト出力されて化けるはず(→次節にて原因判明)
※エラーも出ずに画面も変わらないので、何かサイズ的な問題があるのかもしれず、続きでさらに回答欄を消費します。
投稿2023/12/13 12:34
編集2023/12/15 10:00退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
元々自分も簡単に対応する方法を探していて,解決策はmsysのucrt環境を使うことが必要と
思っていて,具体的な方法については分かっていなかったのですが,ようやくネット情報から探り当てました。
paths with non-ASCII chars on Windowsに,setlocale(LC_ALL, "UTF8")
を呼ぶべしとの回答があって,この方法を試してみました。
この解決策には前提条件があります。
MINGW64
ではなくUCRT64
環境を使うUCRT
なのでおそらくWindows 10以降の対応となる- msys2のEnvironmentsの記事の通り
libstdc++
を利用する - gccを利用する(UCRT版)
- UCRTのbash上では日本語出力OK
- コマンドプロンプトではコードページ 932,65001でも日本語出力OK
前準備
比較的新しいmsys2をインストールしているのであれば,UCRT版のビルド環境は比較的簡単だと思います。
msysのどれかのバッチファイルからシェルを起動して,
$ pacman -S --needed mingw-w64-ucrt-x86_64-toolchain
ぐらいでUCRT版のコンパイラがインストールされるのではないでしょうか?
そして,コンパイルはUCRTのシェルを起動してその中で行います。
対策コードと実行例
utf-8
のソースリストのmain関数の最初の方にsetlocale(LC_ALL, ".UTF8");
を書くだけです。
ヘッダファイルはincludeフォルダからsetlocale
関数があるヘッダをgrepで調べただけなので,本当は違うかもしれません。
c++
1#include <iostream> 2#include <locale.h> // 追加 3int main() { 4 setlocale(LC_ALL, ".UTF8"); // 追加 5 std::cout << "こんにちは" << std::endl; 6 return 0; 7}
コンパイルは普通に質問で提示された方法と同じです。
g++ -g -Wall -static hello.cpp -o hello
最後に手元のWindows 10上での実行例を示します。
ldd
コマンドでOS以外のdllとリンクしていないのも確認できます。
実際はコマンド引数の扱いとかも調べないといけないと思いますが,調べた内容はこの辺りで。
投稿2023/12/12 11:52
編集2023/12/12 11:54総合スコア2138
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
質問への追記が文字数的に困難になってきたので、回答側に記述します。
wcoutへの出力についての追加情報
とりあえずwcoutへの出力方法を以下のサイトで見つけました。
c++
1#include <iostream> 2int main() 3{ 4 std::ios_base::sync_with_stdio(false); 5 //std::locale::global(std::locale{""}); // Doesn't work. 6 std::setlocale(LC_CTYPE,""); 7 std::wcout<<L"Hello world!"<<std::endl; 8 std::wcout<<L"こんにちは世界!"<<std::endl; 9 std::wcout<<L"\U0001F600\U0001F601\U0001F602\U0001F603"<<std::endl; 10 return 0; 11}
引用した部分だけでなくcodecvt(C++17から非推奨)の自作や、gccの記述などもあり、まだ全部読めてませんが、仕様に対する言及も多くとても参考になりそうです。
UCRT64で確認した結果
質問に記載した現象を同じ手順でUCRT64で確認した結果を簡単に記載します。
- msys/msys2-runtime 3.4.6-2
- mingw-w64-ucrt-x86_64-gcc 12.2.0-10
発生している問題・エラーメッセージについて
(1)(2)ともに同じ
試したこと
(1)(2)(3)(4)(5)(6)(8)まで同じ
(7)はちゃんとUCRT
bash
1$ ldd hello 2 ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fffa15f0000) 3 KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7fff9fbf0000) 4 KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7fff9f100000) 5 ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7fff9ecd0000) 6 7$
つまりC言語 locale、C++ localeに関係なくlocaleの変更がない状態ではUCRTでも同じ現象になるということのようです。
今回は記述しませんがチラ見した感じだとC言語 localeのエンコーディング設定をUTF-8に変更することにより cout / wcoutに関係なく日本語の表示が可能になっていました。UCRTの挙動についてはVC++側でも未確認なので、次回調査時はそちらも合わせてlocale周りを調べてみようかと思っています。(ちまちま更新していく感じになります)
MSYS2-mintty上でのロケールと各種出力調査
調査用コード
bash
1for cp in "932" "65001"; do 2 cmd //c chcp $cp 3 for out in "cout" "wcout"; do 4 if [ "$out" == "wcout" ]; then 5 prefix="L" 6 else 7 prefix="" 8 fi 9 for str in "こんにちは世界!" "😊"; do 10 for loc in "" "C" ".932" ".UTF-8"; do 11 cat >hello_locale_x.cpp <<EOF 12#include <iostream> 13int main() 14{ 15 std::setlocale(LC_CTYPE,"$loc"); 16 std::$out<<$prefix"$str"; 17 return 0; 18} 19EOF 20 g++ -g -Wall -static hello_locale_x.cpp -o hello_locale_x 21 echo -n "|$cp|$MSYSTEM|$out|\"$loc\"|$prefix\"$str\"|" 22 ./hello_locale_x 23 echo "|" 24 done 25 done 26 done 27 echo press enter 28 read 29done 30cmd //c chcp 932
調査結果
端末コードページ | MSYSTEM | 出力先 | ロケール(C) | リテラル(ソースUTF8) | 結果 |
---|---|---|---|---|---|
932 | MINGW64 | cout | "" | "こんにちは世界!" | |
932 | MINGW64 | cout | "C" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | MINGW64 | cout | ".932" | "こんにちは世界!" | |
932 | MINGW64 | cout | ".UTF-8" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | MINGW64 | cout | "" | "😊" | |
932 | MINGW64 | cout | "C" | "😊" | ・ |
932 | MINGW64 | cout | ".932" | "😊" | |
932 | MINGW64 | cout | ".UTF-8" | "😊" | ・ |
932 | MINGW64 | wcout | "" | L"こんにちは世界!" | |
932 | MINGW64 | wcout | "C" | L"こんにちは世界!" | |
932 | MINGW64 | wcout | ".932" | L"こんにちは世界!" | |
932 | MINGW64 | wcout | ".UTF-8" | L"こんにちは世界!" | |
932 | MINGW64 | wcout | "" | L"😊" | |
932 | MINGW64 | wcout | "C" | L"😊" | |
932 | MINGW64 | wcout | ".932" | L"😊" | |
932 | MINGW64 | wcout | ".UTF-8" | L"😊" | |
65001 | MINGW64 | cout | "" | "こんにちは世界!" | |
65001 | MINGW64 | cout | "C" | "こんにちは世界!" | こんにちは世界! |
65001 | MINGW64 | cout | ".932" | "こんにちは世界!" | |
65001 | MINGW64 | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
65001 | MINGW64 | cout | "" | "😊" | |
65001 | MINGW64 | cout | "C" | "😊" | 😊 |
65001 | MINGW64 | cout | ".932" | "😊" | |
65001 | MINGW64 | cout | ".UTF-8" | "😊" | 😊 |
65001 | MINGW64 | wcout | "" | L"こんにちは世界!" | |
65001 | MINGW64 | wcout | "C" | L"こんにちは世界!" | |
65001 | MINGW64 | wcout | ".932" | L"こんにちは世界!" | |
65001 | MINGW64 | wcout | ".UTF-8" | L"こんにちは世界!" | |
65001 | MINGW64 | wcout | "" | L"😊" | |
65001 | MINGW64 | wcout | "C" | L"😊" | |
65001 | MINGW64 | wcout | ".932" | L"😊" | |
65001 | MINGW64 | wcout | ".UTF-8" | L"😊" | |
932 | UCRT64 | cout | "" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | UCRT64 | cout | "C" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | UCRT64 | cout | ".932" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
932 | UCRT64 | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
932 | UCRT64 | cout | "" | "😊" | ・ |
932 | UCRT64 | cout | "C" | "😊" | ・ |
932 | UCRT64 | cout | ".932" | "😊" | ・ |
932 | UCRT64 | cout | ".UTF-8" | "😊" | ?? |
932 | UCRT64 | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
932 | UCRT64 | wcout | "C" | L"こんにちは世界!" | |
932 | UCRT64 | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
932 | UCRT64 | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
932 | UCRT64 | wcout | "" | L"😊" | |
932 | UCRT64 | wcout | "C" | L"😊" | |
932 | UCRT64 | wcout | ".932" | L"😊" | |
932 | UCRT64 | wcout | ".UTF-8" | L"😊" | |
65001 | UCRT64 | cout | "" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
65001 | UCRT64 | cout | "C" | "こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | cout | ".932" | "こんにちは世界!" | 縺薙s縺ォ縺。縺ッ荳也阜・ |
65001 | UCRT64 | cout | ".UTF-8" | "こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | cout | "" | "😊" | ・ |
65001 | UCRT64 | cout | "C" | "😊" | 😊 |
65001 | UCRT64 | cout | ".932" | "😊" | ・ |
65001 | UCRT64 | cout | ".UTF-8" | "😊" | 😊 |
65001 | UCRT64 | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | wcout | "C" | L"こんにちは世界!" | |
65001 | UCRT64 | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
65001 | UCRT64 | wcout | "" | L"😊" | |
65001 | UCRT64 | wcout | "C" | L"😊" | |
65001 | UCRT64 | wcout | ".932" | L"😊" | |
65001 | UCRT64 | wcout | ".UTF-8" | L"😊" |
現時点での考察
mingw64/ucrt64環境では、設定ロケールを端末コードページに合わせて出力されるように見える。
mingw64では、ロケールがUTF-8("C"ロケール含む)のときだけ出力しようとするが、実際の変換が行われない。またwcoutの場合はロケールが何であっても出力できない。
ucrt64環境では、実際の変換も行われており"C"ロケールのときがやや特殊な変換なのと、wcoutで絵文字が出ない点を除き、想定どおりの動作をしている。
なので、現時点でVC++で作成したアプリと近い動きを期待する(locale設定に""を使い、システムロケールに依存した動きにする)ためには、UCRT64でワイド文字列リテラルとwcoutを使うことが望ましいと予想する。ただし絵文字(他にもあるかも)は諦める必要がある。
次はVC++でのUCRTの動きと、コマンドプロンプト上での動作確認でしょうか。。。
VC++でビルドしたものを動作確認
絵文字表示が可能な端末が必要だったのでminttyでも確認。ビルドはVS2019を使用。
スクリプト
CMake
1cmake_minimum_required (VERSION 3.8) 2project ("hello_locale_x") 3add_definitions(-DUNICODE -D_UNICODE) 4add_executable (hello_locale_x "hello_locale_x.cpp" )
cmake -B build -S .
くらいしてから~~powershellを流す。~~以下のC++プログラムを流します。
C++
1#include <iostream> 2#include <string> 3#include <fstream> 4#include <cstdlib> 5#include <windows.h> 6using namespace std; 7int main(int argc, char* argv[]) { 8 setlocale(LC_CTYPE, "C"); 9 10 bool build = (argc == 1); // 引数が1つでも指定されてたらビルドしない 11 UINT cps[] = { 932, CP_UTF8 }; 12 constexpr size_t CP_COUNT = sizeof(cps) / sizeof(cps[0]); 13 wstring strs[] = { L"こんにちは世界!", L"😊" }; 14 constexpr size_t TEST_COUNT = sizeof(strs) / sizeof(strs[0]); 15 16 for (int cp = 0; cp < CP_COUNT; ++cp) { 17 system((string("chcp ") + to_string(cps[cp])).c_str()); 18 for (string out : {"cout", "wcout"}) { 19 string prefix; 20 if (out == string("wcout")) { 21 prefix = "L"; 22 } 23 for (int i = 0; i < TEST_COUNT; ++i) { 24 char bytes[100]; 25 constexpr int BYTES_COUNT = sizeof(bytes) / sizeof(bytes[0]); 26 auto l = WideCharToMultiByte(cps[cp], 0, strs[i].c_str(), static_cast<int>(strs[i].size()), bytes, BYTES_COUNT, cps[cp] == CP_UTF8 ? NULL : "?", NULL); 27 string cpstr(bytes, l); 28 l = WideCharToMultiByte(CP_UTF8, 0, strs[i].c_str(), static_cast<int>(strs[i].size()), bytes, BYTES_COUNT, NULL, NULL); 29 string u8str(bytes, l); 30 31 for (string loc : {"", "C", ".932", ".UTF-8"}) { 32 string fname = string("hello_locale_") + to_string(cps[cp]) + "_" + out + "_" + loc + "_" + to_string(i); 33 if (build) { 34 ofstream f("hello_locale_x.cpp"); 35 f << "\ 36\xEF\xBB\xBF\ 37#include <iostream>\n\ 38int main()\n\ 39{\n\ 40 std::setlocale(LC_CTYPE, \"" << loc << "\");\n\ 41 std::" << out << " << " << prefix << "\"" << u8str << "\";\n\ 42 return 0;\n\ 43}"; 44 f.close(); 45 auto r = system("cmake --build build >NUL"); 46 if (r != 0) return 1; 47 system("copy .\\build\\Debug\\hello_locale_x.exe . >nul"); 48 system((string("copy hello_locale_x.exe ") + fname + ".exe >nul").c_str()); 49 system((string("copy hello_locale_x.cpp ") + fname + ".cpp >nul").c_str()); 50 } 51 else { 52 system((string("copy " + fname + ".exe hello_locale_x.exe >nul")).c_str()); 53 } 54 cout << "|" << cps[cp] << "| MSVC++ |" << out << "|\"" << loc << "\"|" << prefix << "\"" << cpstr << "\"|"; 55 cout.flush(); 56 system(".\\hello_locale_x"); 57 cout << "|" << endl; 58 } 59 } 60 } 61 cerr << "press enter" << endl; 62 string _; 63 std::getline(cin, _); 64 } 65 system("chcp 932"); 66 return 0; 67}
結果(mintty)
端末コードページ | 処理系 | 出力先 | ロケール(C) | リテラル(ソースUTF8) | 結果 |
---|---|---|---|---|---|
932 | MSVC++ | cout | "" | "こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | cout | "C" | "こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | cout | ".932" | "こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | cout | ".UTF-8" | "こんにちは世界!" | |
932 | MSVC++ | cout | "" | "😊" | ?? |
932 | MSVC++ | cout | "C" | "😊" | ?? |
932 | MSVC++ | cout | ".932" | "😊" | ?? |
932 | MSVC++ | cout | ".UTF-8" | "😊" | ?? |
932 | MSVC++ | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | "C" | L"こんにちは世界!" | |
932 | MSVC++ | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
932 | MSVC++ | wcout | "" | L"😊" | |
932 | MSVC++ | wcout | "C" | L"😊" | |
932 | MSVC++ | wcout | ".932" | L"😊" | |
932 | MSVC++ | wcout | ".UTF-8" | L"😊" | |
65001 | MSVC++ | cout | "" | "こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | cout | "C" | "こんにちは世界!" | ɂ͐EI |
65001 | MSVC++ | cout | ".932" | "こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | cout | ".UTF-8" | "こんにちは世界!" | |
65001 | MSVC++ | cout | "" | "😊" | ?? |
65001 | MSVC++ | cout | "C" | "😊" | ?? |
65001 | MSVC++ | cout | ".932" | "😊" | ?? |
65001 | MSVC++ | cout | ".UTF-8" | "😊" | ?? |
65001 | MSVC++ | wcout | "" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | "C" | L"こんにちは世界!" | |
65001 | MSVC++ | wcout | ".932" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | ".UTF-8" | L"こんにちは世界!" | こんにちは世界! |
65001 | MSVC++ | wcout | "" | L"😊" | |
65001 | MSVC++ | wcout | "C" | L"😊" | |
65001 | MSVC++ | wcout | ".932" | L"😊" | |
65001 | MSVC++ | wcout | ".UTF-8" | L"😊" |
結果(コマンドプロンプト)
😊が表示不可能だけで同じ
現時点での考察
マルチバイト時文字列リテラルがバイナリ上シフトJISになっていることが発覚。後日考察。本当にシステムロケールUTF-8で動作するのか?
投稿2023/04/01 22:25
編集2023/12/14 05:32退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
これは難しい問題です。 文字コードに関しては場当たり的な対処で運用されていて、こうすればすべて解決するというような決定的な方法がありません。
なぜ化けるか
まず、 Windows のコンソールはコードページに従って解釈します。 つまりデフォルトではプログラムからの出力を CP932 のつもりで読み取ります。 CP932 のつもりで読み取るのでプログラムから出力しているのが UTF-8 だったら化けてしまいます。
状況を場合分けして考えます。
GCC のオプション
CP932 に設定された状態で化けずにコンソールに出力されれば良いのであれば GCC のオプション -fexec-charset
で CP932 を指定すれば良いです。 ソースコード中にある文字列リテラルを実行ファイルには CP932 で格納するという意味のオプションです。
GCC をビルドするときに iconv とリンクする設定になっていないとこのオプションは使えないのですが普通に MSYS2 経由で入れた GCC なら使えるはずです。
しかしこの方法では CP932 に無い文字は扱えません。 表す符号が存在しないからです。
コンソール API
プログラムが CP932 で出力してもコンソールの側の設定が変わっていて他の文字コードのつもりで読み取ったらやっぱり破綻してしまいます。
ここで利用できるのは WriteConsoleOutput
API です。 パイプを通じてコンソールとやり取りするのではなくコンソールの描画を直接的につかさどる API です。 文字列を扱う Windows API の多くがそうであるようにこの API は ASCII の WriteConsoleOutputA
と WriteConsoleOutputW
があり、 WriteConsoleOutputW
は常に Unicode を使います。 (Windows API について単に Unicode と書いてある場合には UTF-16 のことを意味します。)
しかし Windows コンソール上以外では使えません。 MSYS2 のターミナル (mintty) などでは使えません。
新しい API
近頃の Windows では擬似コンソール (ConPTY) と呼ばれる仕組みがあります。 要はターミナルソフトと一貫したインターフェイスでやり取りする方法を整備しようという話で、文字コードに関しての最終的な解決策と言えるでしょう。
ConPTY に対応したターミナルとしては Windows Terminal が代表的なものです。 VSCode も ConPTY に対応しているので知らずに使っているかもしれません……、が、結局のところは ConPTY に対応してないとどうにもならないので従来のコンソールでどうすべきかという解決策にはなりません。
落としどころ
プログラムの側は UTF-8 で出力するようにしてターミナルの設定も UTF-8 にしておくというのが全体としては楽な運用であろうかと思われます。
mintty も UTF-8 に変えられます。 mintty のウィンドウのタイトルバーの上で右クリックして option を選択してください。
その上で旧来の (UTF-8 を使っていない) Windows 用ソフトは winpty コマンド経由で呼び出すと文字化けせずに使えることが多いです。 winpty はそのためのコマンドです。
投稿2023/04/01 11:23
総合スコア5604
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2023/04/01 11:56
2023/04/01 15:45
退会済みユーザー
2023/04/01 21:07
2023/04/02 15:40
退会済みユーザー
2023/04/13 08:27
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。