質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

MinGW

MinGW(ミン・ジー・ダブリュー)は GNUツールチェーンのWindows移植版です。 ランタイムライブラリと開発ツールで構成されています。

Q&A

解決済

7回答

4233閲覧

MINGW64で作ったC++プログラムで正しく日本語を扱いたい

退会済みユーザー

退会済みユーザー

総合スコア0

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

MinGW

MinGW(ミン・ジー・ダブリュー)は GNUツールチェーンのWindows移植版です。 ランタイムライブラリと開発ツールで構成されています。

0グッド

5クリップ

投稿2023/04/01 08:35

編集2023/12/18 17:30

実現したいこと

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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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→MBMB→WideWide→MB出力先結果
932UCRT64cout".UTF8""日本語"ucrtbase!fwrite→932端末"日本語"
932UCRT64wcout".UTF8"L"日本語"ucrtbase!fputwc→932端末"日本語"
932UCRT64cout"""日本語"ucrtbase!fwrite932→→932端末化けた
932UCRT64wcout""L"日本語"ucrtbase!fputwc→932932→→932端末"日本語"
932UCRT64cout"C""日本語"ucrtbase!fwrite端末"譌・譛ャ隱"
932UCRT64wcout"C"L"日本語"ucrtbase!fputwc端末""
65001UCRT64cout".UTF8""日本語"ucrtbase!fwrite→65001端末"日本語"
65001UCRT64wcout".UTF8"L"日本語"ucrtbase!fputwc→65001端末"日本語"
65001UCRT64cout"""日本語"ucrtbase!fwrite932→→65001端末化けた
65001UCRT64wcout""L"日本語"ucrtbase!fputwc→932932→→65001端末"日本語"
65001UCRT64cout"C""日本語"ucrtbase!fwrite端末"日本語"
65001UCRT64wcout"C"L"日本語"ucrtbase!fputwc端末""
932UCRT64cout".UTF8""日本語"ucrtbase!fwriteリダイレクト"譌・譛ャ隱"
932UCRT64wcout".UTF8"L"日本語"ucrtbase!fputwcリダイレクト"譌・譛ャ隱"
932UCRT64cout"""日本語"ucrtbase!fwriteリダイレクト"譌・譛ャ隱"
932UCRT64wcout""L"日本語"ucrtbase!fputwc→932リダイレクト"日本語"
932UCRT64cout"C""日本語"ucrtbase!fwriteリダイレクト"譌・譛ャ隱"
932UCRT64wcout"C"L"日本語"ucrtbase!fputwcリダイレクト""
65001UCRT64cout".UTF8""日本語"ucrtbase!fwriteリダイレクト"日本語"
65001UCRT64wcout".UTF8"L"日本語"ucrtbase!fputwcリダイレクト"日本語"
65001UCRT64cout"""日本語"ucrtbase!fwriteリダイレクト"日本語"
65001UCRT64wcout""L"日本語"ucrtbase!fputwc→932リダイレクト"▒▒▒{▒▒"
65001UCRT64cout"C""日本語"ucrtbase!fwriteリダイレクト"日本語"
65001UCRT64wcout"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入り口
linuxtruecout_IO_new_file_overflow(fputc)
linuxtruewcout__GI_putwc(fputwc)
linuxfalsecout__GI___libc_write(write)
linuxfalsewcout止まらない
mingw64truecoutmsvcrt!fwrite
mingw64truewcoutmsvcrt!fputwc
mingw64falsecoutmsvcrt!_write
mingw64falsewcoutmsvcrt!_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が選ばれている。

http://www17.plala.or.jp/KodamaDeveloped/LetsProgramming/details_how_to_develop_japanese_application_codecvt_libstdcpp_source.html

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)結果
932MINGW64cout"""こんにちは世界!"
932MINGW64cout"C""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932MINGW64cout".932""こんにちは世界!"
932MINGW64cout".UTF-8""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932MINGW64cout"""😊"
932MINGW64cout"C""😊"・
932MINGW64cout".932""😊"
932MINGW64cout".UTF-8""😊"・
932MINGW64wcout""L"こんにちは世界!"
932MINGW64wcout"C"L"こんにちは世界!"
932MINGW64wcout".932"L"こんにちは世界!"
932MINGW64wcout".UTF-8"L"こんにちは世界!"
932MINGW64wcout""L"😊"
932MINGW64wcout"C"L"😊"
932MINGW64wcout".932"L"😊"
932MINGW64wcout".UTF-8"L"😊"
65001MINGW64cout"""こんにちは世界!"
65001MINGW64cout"C""こんにちは世界!"こんにちは世界!
65001MINGW64cout".932""こんにちは世界!"
65001MINGW64cout".UTF-8""こんにちは世界!"こんにちは世界!
65001MINGW64cout"""😊"
65001MINGW64cout"C""😊"😊
65001MINGW64cout".932""😊"
65001MINGW64cout".UTF-8""😊"😊
65001MINGW64wcout""L"こんにちは世界!"
65001MINGW64wcout"C"L"こんにちは世界!"
65001MINGW64wcout".932"L"こんにちは世界!"
65001MINGW64wcout".UTF-8"L"こんにちは世界!"
65001MINGW64wcout""L"😊"
65001MINGW64wcout"C"L"😊"
65001MINGW64wcout".932"L"😊"
65001MINGW64wcout".UTF-8"L"😊"
932UCRT64cout"""こんにちは世界!"こんにちは世界!
932UCRT64cout"C""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932UCRT64cout".932""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932UCRT64cout".UTF-8""こんにちは世界!"こんにちは世界!
932UCRT64cout"""😊"??
932UCRT64cout"C""😊"・
932UCRT64cout".932""😊"・
932UCRT64cout".UTF-8""😊"??
932UCRT64wcout""L"こんにちは世界!"こんにちは世界!
932UCRT64wcout"C"L"こんにちは世界!"
932UCRT64wcout".932"L"こんにちは世界!"こんにちは世界!
932UCRT64wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
932UCRT64wcout""L"😊"
932UCRT64wcout"C"L"😊"
932UCRT64wcout".932"L"😊"
932UCRT64wcout".UTF-8"L"😊"
65001UCRT64cout"""こんにちは世界!"こんにちは世界!
65001UCRT64cout"C""こんにちは世界!"こんにちは世界!
65001UCRT64cout".932""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
65001UCRT64cout".UTF-8""こんにちは世界!"こんにちは世界!
65001UCRT64cout"""😊"😊
65001UCRT64cout"C""😊"😊
65001UCRT64cout".932""😊"・
65001UCRT64cout".UTF-8""😊"😊
65001UCRT64wcout""L"こんにちは世界!"こんにちは世界!
65001UCRT64wcout"C"L"こんにちは世界!"
65001UCRT64wcout".932"L"こんにちは世界!"こんにちは世界!
65001UCRT64wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
65001UCRT64wcout""L"😊"
65001UCRT64wcout"C"L"😊"
65001UCRT64wcout".932"L"😊"
65001UCRT64wcout".UTF-8"L"😊"
932MSVC++cout"""こんにちは世界!"こんにちは世界!
932MSVC++cout"C""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・-
932MSVC++cout".932""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932MSVC++cout".UTF-8""こんにちは世界!"こんにちは世界!
932MSVC++cout"""😊"??
932MSVC++cout"C""😊"・
932MSVC++cout".932""😊"・
932MSVC++cout".UTF-8""😊"??
932MSVC++wcout""L"こんにちは世界!"こんにちは世界!
932MSVC++wcout"C"L"こんにちは世界!"
932MSVC++wcout".932"L"こんにちは世界!"こんにちは世界!
932MSVC++wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
932MSVC++wcout""L"😊"
932MSVC++wcout"C"L"😊"
932MSVC++wcout".932"L"😊"
932MSVC++wcout".UTF-8"L"😊"
65001MSVC++cout"""こんにちは世界!"こんにちは世界!
65001MSVC++cout"C""こんにちは世界!"こんにちは世界!
65001MSVC++cout".932""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
65001MSVC++cout".UTF-8""こんにちは世界!"こんにちは世界!
65001MSVC++cout"""😊"😊
65001MSVC++cout"C""😊"😊
65001MSVC++cout".932""😊"・
65001MSVC++cout".UTF-8""😊"😊
65001MSVC++wcout""L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout"C"L"こんにちは世界!"
65001MSVC++wcout".932"L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout""L"😊"
65001MSVC++wcout"C"L"😊"
65001MSVC++wcout".932"L"😊"
65001MSVC++wcout".UTF-8"L"😊"

システムロケールCP932で作成したバイナリを、システムロケールUTF-8で実行した結果

端末コードページ処理系出力先ロケール(C)リテラル(ソースUTF8)結果
932MSVC++cout"""こんにちは世界!"
932MSVC++cout"C""こんにちは世界!"こんにちは世界!
932MSVC++cout".932""こんにちは世界!"こんにちは世界!
932MSVC++cout".UTF-8""こんにちは世界!"
932MSVC++cout"""😊"??
932MSVC++cout"C""😊"??
932MSVC++cout".932""😊"??
932MSVC++cout".UTF-8""😊"??
932MSVC++wcout""L"こんにちは世界!"こんにちは世界!
932MSVC++wcout"C"L"こんにちは世界!"
932MSVC++wcout".932"L"こんにちは世界!"こんにちは世界!
932MSVC++wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
932MSVC++wcout""L"😊"
932MSVC++wcout"C"L"😊"
932MSVC++wcout".932"L"😊"
932MSVC++wcout".UTF-8"L"😊"
65001MSVC++cout"""こんにちは世界!"
65001MSVC++cout"C""こんにちは世界!"ɂ͐EI
65001MSVC++cout".932""こんにちは世界!"こんにちは世界!
65001MSVC++cout".UTF-8""こんにちは世界!"
65001MSVC++cout"""😊"??
65001MSVC++cout"C""😊"??
65001MSVC++cout".932""😊"??
65001MSVC++cout".UTF-8""😊"??
65001MSVC++wcout""L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout"C"L"こんにちは世界!"
65001MSVC++wcout".932"L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout""L"😊"
65001MSVC++wcout"C"L"😊"
65001MSVC++wcout".932"L"😊"
65001MSVC++wcout".UTF-8"L"😊"

システムロケールUTF-8で作成したバイナリを、システムロケールCP932で実行した結果

端末コードページ処理系出力先ロケール(C)リテラル(ソースUTF8)結果
932MSVC++cout"""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932MSVC++cout"C""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・-
932MSVC++cout".932""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932MSVC++cout".UTF-8""こんにちは世界!"こんにちは世界!
932MSVC++cout"""😊"・
932MSVC++cout"C""😊"・
932MSVC++cout".932""😊"・
932MSVC++cout".UTF-8""😊"??
932MSVC++wcout""L"こんにちは世界!"こんにちは世界!
932MSVC++wcout"C"L"こんにちは世界!"
932MSVC++wcout".932"L"こんにちは世界!"こんにちは世界!
932MSVC++wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
932MSVC++wcout""L"😊"
932MSVC++wcout"C"L"😊"
932MSVC++wcout".932"L"😊"
932MSVC++wcout".UTF-8"L"😊"
65001MSVC++cout"""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
65001MSVC++cout"C""こんにちは世界!"こんにちは世界!
65001MSVC++cout".932""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
65001MSVC++cout".UTF-8""こんにちは世界!"こんにちは世界!
65001MSVC++cout"""😊"・
65001MSVC++cout"C""😊"😊
65001MSVC++cout".932""😊"・
65001MSVC++cout".UTF-8""😊"😊
65001MSVC++wcout""L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout"C"L"こんにちは世界!"
65001MSVC++wcout".932"L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout""L"😊"
65001MSVC++wcout"C"L"😊"
65001MSVC++wcout".932"L"😊"
65001MSVC++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")を呼ぶべしとの回答があって,この方法を試してみました。

この解決策には前提条件があります。

  1. MINGW64ではなくUCRT64環境を使う
  2. UCRTなのでおそらくWindows 10以降の対応となる
  3. msys2のEnvironmentsの記事の通りlibstdc++を利用する
  4. gccを利用する(UCRT版)
  5. UCRTのbash上では日本語出力OK
  6. コマンドプロンプトではコードページ 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
ujimushi_sradjp

総合スコア2150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2023/12/12 12:31

SaitoAtsushiさんに言われてやるつもりでいたのですがすっかり忘れてましたね。見た感じSaitoAtsushiさんのおっしゃるとおりのようですね。後で自分でもとりあえず質問に書いたことを一通りUCRTでやってみます。codecvtの兼ね合いなども含めて調べたいのですが、それはまた今度かなという感じになりそうです。 ただWindowsでVC++を使わないアプリではMINGW64(MSVCRT)以外を見たことがなく、UCRTの使用状況が分からないので、どちらで何が出来て何が出来ないのか、どうしてなのか、状況の把握はもう少しきちんとしたいですね。
guest

0

質問への追記が文字数的に困難になってきたので、回答側に記述します。

wcoutへの出力についての追加情報

とりあえずwcoutへの出力方法を以下のサイトで見つけました。

http://www17.plala.or.jp/KodamaDeveloped/LetsProgramming/details_how_to_develop_japanese_application.html

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)結果
932MINGW64cout"""こんにちは世界!"
932MINGW64cout"C""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932MINGW64cout".932""こんにちは世界!"
932MINGW64cout".UTF-8""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932MINGW64cout"""😊"
932MINGW64cout"C""😊"・
932MINGW64cout".932""😊"
932MINGW64cout".UTF-8""😊"・
932MINGW64wcout""L"こんにちは世界!"
932MINGW64wcout"C"L"こんにちは世界!"
932MINGW64wcout".932"L"こんにちは世界!"
932MINGW64wcout".UTF-8"L"こんにちは世界!"
932MINGW64wcout""L"😊"
932MINGW64wcout"C"L"😊"
932MINGW64wcout".932"L"😊"
932MINGW64wcout".UTF-8"L"😊"
65001MINGW64cout"""こんにちは世界!"
65001MINGW64cout"C""こんにちは世界!"こんにちは世界!
65001MINGW64cout".932""こんにちは世界!"
65001MINGW64cout".UTF-8""こんにちは世界!"こんにちは世界!
65001MINGW64cout"""😊"
65001MINGW64cout"C""😊"😊
65001MINGW64cout".932""😊"
65001MINGW64cout".UTF-8""😊"😊
65001MINGW64wcout""L"こんにちは世界!"
65001MINGW64wcout"C"L"こんにちは世界!"
65001MINGW64wcout".932"L"こんにちは世界!"
65001MINGW64wcout".UTF-8"L"こんにちは世界!"
65001MINGW64wcout""L"😊"
65001MINGW64wcout"C"L"😊"
65001MINGW64wcout".932"L"😊"
65001MINGW64wcout".UTF-8"L"😊"
932UCRT64cout"""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932UCRT64cout"C""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932UCRT64cout".932""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
932UCRT64cout".UTF-8""こんにちは世界!"こんにちは世界!
932UCRT64cout"""😊"・
932UCRT64cout"C""😊"・
932UCRT64cout".932""😊"・
932UCRT64cout".UTF-8""😊"??
932UCRT64wcout""L"こんにちは世界!"こんにちは世界!
932UCRT64wcout"C"L"こんにちは世界!"
932UCRT64wcout".932"L"こんにちは世界!"こんにちは世界!
932UCRT64wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
932UCRT64wcout""L"😊"
932UCRT64wcout"C"L"😊"
932UCRT64wcout".932"L"😊"
932UCRT64wcout".UTF-8"L"😊"
65001UCRT64cout"""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
65001UCRT64cout"C""こんにちは世界!"こんにちは世界!
65001UCRT64cout".932""こんにちは世界!"縺薙s縺ォ縺。縺ッ荳也阜・
65001UCRT64cout".UTF-8""こんにちは世界!"こんにちは世界!
65001UCRT64cout"""😊"・
65001UCRT64cout"C""😊"😊
65001UCRT64cout".932""😊"・
65001UCRT64cout".UTF-8""😊"😊
65001UCRT64wcout""L"こんにちは世界!"こんにちは世界!
65001UCRT64wcout"C"L"こんにちは世界!"
65001UCRT64wcout".932"L"こんにちは世界!"こんにちは世界!
65001UCRT64wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
65001UCRT64wcout""L"😊"
65001UCRT64wcout"C"L"😊"
65001UCRT64wcout".932"L"😊"
65001UCRT64wcout".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)結果
932MSVC++cout"""こんにちは世界!"こんにちは世界!
932MSVC++cout"C""こんにちは世界!"こんにちは世界!
932MSVC++cout".932""こんにちは世界!"こんにちは世界!
932MSVC++cout".UTF-8""こんにちは世界!"
932MSVC++cout"""😊"??
932MSVC++cout"C""😊"??
932MSVC++cout".932""😊"??
932MSVC++cout".UTF-8""😊"??
932MSVC++wcout""L"こんにちは世界!"こんにちは世界!
932MSVC++wcout"C"L"こんにちは世界!"
932MSVC++wcout".932"L"こんにちは世界!"こんにちは世界!
932MSVC++wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
932MSVC++wcout""L"😊"
932MSVC++wcout"C"L"😊"
932MSVC++wcout".932"L"😊"
932MSVC++wcout".UTF-8"L"😊"
65001MSVC++cout"""こんにちは世界!"こんにちは世界!
65001MSVC++cout"C""こんにちは世界!"ɂ͐EI
65001MSVC++cout".932""こんにちは世界!"こんにちは世界!
65001MSVC++cout".UTF-8""こんにちは世界!"
65001MSVC++cout"""😊"??
65001MSVC++cout"C""😊"??
65001MSVC++cout".932""😊"??
65001MSVC++cout".UTF-8""😊"??
65001MSVC++wcout""L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout"C"L"こんにちは世界!"
65001MSVC++wcout".932"L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout".UTF-8"L"こんにちは世界!"こんにちは世界!
65001MSVC++wcout""L"😊"
65001MSVC++wcout"C"L"😊"
65001MSVC++wcout".932"L"😊"
65001MSVC++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 の WriteConsoleOutputAWriteConsoleOutputW があり、 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

SaitoAtsushi

総合スコア5675

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2023/04/01 11:56

すみません。なぜ化けるか?は一体どこで何を実行したときの話ですか? -fexec-charsetは、u8/Lプレフィックスを付けた文字列リテラルでもそうだということですか? 確認してないのですが、コンソールAPIはminttyで使えないのですか? ConPTYはMSYS2、つまりminttyでも対応しているのでしょうか? WindowsTerminalは基本的に従来の慣習で幾層にも機能が分散した端末制御を統一するためのものだと聞いたことがあります。その際に新しいAPIセットを使うといってたのがConPTYなのかなぁと思っています。 落とし所というか、cmd.exeなどを使用する場合、結局端末側のchcpなどで設定されるエンコーディングと、システムロケール(といってもユーザー設定だけど)のどちらかに依存した出力が必要という話ですよね? そしてその出力方法をC++で「実装上」どう制御するのがかがこの質問の主旨です。 そういう意味では、この回答は端末の設定の話であって、ちょっと意図から外れている、と思います。 また、端末だけで説明のつかない現象が出ているMSYS2を使う場合なども考慮して頂けると助かります。 winptyは単品動作という前提から大きく外れるので、関係ありません。
SaitoAtsushi

2023/04/01 15:45

> なぜ化けるか?は一体どこで何を実行したときの話ですか? コンソールは Windows 用語のコンソールです。 UTF-8 で出力するプログラム結果が (CP932 のつもりで読む) コンソールに流し込まれればという意味ですが、コンソールに限らずデータを書く側と読む側が文字コードについて違う想定を持っていれば化けるという当たり前の前提を確認する文言です。 > -fexec-charsetは、u8/Lプレフィックスを付けた文字列リテラルでもそうだということですか? いいえ。 プレフィクスなしの文字列リテラルについてです。 ワイド文字リテラルについては -fwide-exec-charset というオプションがあります。 u8 付きのリテラルは実行時にも常に UTF-8 です。 C++ の言語仕様としては char や wchar_t は制約がかなり少なくて文字コードは処理系定義ですので処理系の裁量で好きなようにできる余地があるのです。 > 確認してないのですが、コンソールAPIはminttyで使えないのですか? 直接的には使えません。 mintty はパイプで接続されているだけですのでコンソール API の影響を受けません。 それをなんとかするのが winpty コマンドです。 非表示のコンソールを作ってそれに結果を流し込ませた上でバッファの内容を読み取るという手順をとっています。 コンソールがどう解釈するのかは実際にコンソールに解釈させてみればよいという発想に基づくものです。 本物にやらせれば互換性が確保できるというのは実に面白いアイデアです。 ちなみにパイプには名前が付けられるので名前から接続先を判断して挙動を変えるソフトウェアというものもあります。 私が知っている代表的な例として vim や gauche があります。 https://github.com/vim/vim/blob/35d7a2fb13fc833aa1b654ca6fd6e429e72e6b49/src/iscygpty.c#L113-L173 https://github.com/shirok/Gauche/blob/9f368a32b45ce65fd66e404198d8ecf025e27504/src/libsys.scm#L1542-L1548 MSYS2 や Cygwin に対して特別な配慮をしています。 > そしてその出力方法をC++で「実装上」どう制御するのがかがこの質問の主旨です。 説明が迂遠でしたので端的に言いかえます。 方法は無いです。 (少なくとも私が把握している範囲で決定的な方法は。) コンソールは Windows の一部ですしコンソールだけのことを言うのであればコンソールAPIでよいのですが様々なソフトがどう解釈するか、どういう規格に対応しているかを全て知ることは出来ません。 コンソールAPIも段階的に停止して新しい API に置き換えていくと明言されており、なんらかの方法があったとしても事情が変わっていくのもよくある話です。 なので「主要なもの (現代なら UTF-8) に対応しておく」ということしかできることがなく、それで不都合があるようならその場その場で対処するしか仕方がないと考えます。 上述の vim は超メジャーなソフトウェアですが利用者も開発者も多いそんな有名ソフトでも Windows 一般の機能では対処できずに接続先のターミナルが有名なやつだったら特別な配慮をするという実に場当たり的な処置をしているのです。 > 端末だけで説明のつかない現象が出ている 確かに変な挙動ですね……。 これについてはざっと見ただけでは私にはわかりませんでした。 「縺薙s縺ォ縺。縺ッ」という文字列が UTF-8 を CP932 として解釈したときに出てくるものだというのは明白なのですがこの場合にそうなる理屈はわかりませんね。
退会済みユーザー

退会済みユーザー

2023/04/01 21:07

minttyがコンソールAPIを実装したアプリケーションではなく、conhost.exeをパイプで中継したアプリケーションに過ぎないというのは知りませんでした。プロセスを除いたら確かに浮いてるconhostがmittyの近くにいて、生成/消滅が同期しているのは確認しました。それを知らなかったので、mintty上で動作してるアプリはコンソールAPIを使えないのか?だとしたら表示がそもそも無理では?とWriteConsoleしてみたりしてたのですが、そういう話ではないのですね。 WinPtyの話はConsole APIでガチガチになったWindowsに他のOS同様pty=pseudo-tty=擬似端末デバイスを作るという話だったんですね。確かにないと困るよね、とは思いますが、文字化け対処にこれが関係するのがいいかはよく分かりません。cygiwn/MSYS2は端末のコードページを見て挙動を変えるようですが、Windowsでは、そもそも端末に興味を持たず、システムロケール(GetACP()の結果)だけで切り替わるソフトも多いです。早い話がchcpしても動作を変えないわけです。 そんな中、cygwinやmsys2などが頑張っているのは、システムロケールがUTF-8のサブセットでない言語の人が、chcpしたり端末変えるなど並々ならぬ努力をして、Unix的な機能やアプリをUTF-8でアドホックに使えるようにしたいからだと思っています。そんな努力の一環として、vimなどがpty判定のために頑張ったりしてるのだと思うのですが、個人的にはwindowsにconsole api→ptyという流れで一安心というよりは、とりあえずmingw64なソフトがcygwin/msys2以外の環境で何も考えずに日本語でcout/wcoutが使えるようにしたい、できないのなら現状で一番良さげな方法を知りたいというだけなのです。コード書いてるときはmsys2/mingw64も使うし、不可解な挙動も原因が分かれば、それ向けの対処もできるかもしれないし、文字化け対処の観点からも何かいい方法が見つかったりしないかな、と思ってはいますが(もちろん単純に知りたいとも思っている)、アドホックなUTF-8で統一された環境を整えるのに役立つ何かを作りたいというわけではありません。 あくまでmingw64でstaticリンクした.exeがcygwin/msys2環境に依存しない形で、VC++で作ったソフトと同様に使えればいいなぁというだけなのです。(今回は正規表現の速度調査をWindowsでやってたらVC++が思ったより遅かったので、mingw64でも試したいだけ) 何はともあれ、情報ありがとうございます。
SaitoAtsushi

2023/04/02 15:40

MSVCRT が UTF-8 ロケールに対応していないようです。 UCRT への置き換えを進めているのはおそらく MSVCRT が根本的にダメで (少なくとも互換性を維持した形では) 改良のしようがないということなんだと思います。 逆に言えば UCRT にすればまあまあ楽に UTF-8 で運用できそうです。
退会済みユーザー

退会済みユーザー

2023/04/13 08:27

なぜか毎回通知がないので、今しがた発見して反応が遅れました。 UCRTは個人的にUniversalという言葉に拒否反応が出ていて、全然追っかけていませんでした。これは調査した方が良さそうですね。 今はちょっと時間が取れそうになく、しばらく後回しになりそうですが、いずれ。。。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問