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

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

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

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

MinGW

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

Q&A

受付中

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

dameo
dameo

総合スコア787

C++

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

MinGW

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

2回答

0グッド

5クリップ

550閲覧

投稿2023/04/01 08:35

編集2023/04/02 14:48

実現したいこと

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

前提

  • MSYS2-MINGW64使用(※)
  • MSVCのRuntimeを使用する
  • libstdc++を使用する
  • gccを使用する(現状12.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

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

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

下記のような質問は推奨されていません。

  • 質問になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

適切な質問に修正を依頼しましょう。

回答2

0

質問への追記が文字数的に困難になってきたので、回答側に記述します。
とりあえず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の記述などもあり、まだ全部読めてませんが、仕様に対する言及も多くとても参考になりそうです。

投稿2023/04/01 22:25

dameo

総合スコア787

下記のような回答は推奨されていません。

  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

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

総合スコア5105

下記のような回答は推奨されていません。

  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

dameo

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 として解釈したときに出てくるものだというのは明白なのですがこの場合にそうなる理屈はわかりませんね。
dameo

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 で運用できそうです。
dameo

2023/04/13 08:27

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.83%

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

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

質問する

関連した質問

同じタグがついた質問を見る

C++

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

MinGW

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