ソースコードはUTF-8のままにして、WSL上でコンパイルしてください。
【解説】(凄く長い)
C++での文字列リテラル "~"
という表記はconst char[N]
型の ナローマルチバイト文字列リテラル(narrow multibyte string literal) です。ナローマルチバイト文字列とはシングルバイト文字列(ASCII等)やマルチバイト文字列(UTF-8、Shift_JIS等)のことを意味します。バイナリ上でどのエンコードになるのかは環境依存であり、環境のデフォルトまたはコンパイラオプションによって決定されます。
環境のデフォルトはOSやロケール設定によって異なります。Windowsであれば、デフォルトはANSIと言われる現在のロケールに合わせたASCII互換な文字コードです。日本語ロケールの場合は Windows-31J というShift_JISの亜種(Shift_JISとは一部が異なる)であり、Windows内部では932というコードページが割り当てられています(そのため、CP932、MS932とも言われます)。最新のmacOSやLinuxではUTF-8になっています(日本語ロケール未設定の場合はen_US.UTF-8で、日本語ロケールにするとja_JP.UTF-8になるという形がほとんどです)。WSL上のUbuntuの場合はLinuxとして設定されるため、UTF-8がデフォルトになります。Windows側が日本語ロケールであれば、環境変数LANG
がja_JP.UTF-8
になっている事でも確認できるはずです。
おっとここで、先に進む前にターミナルの話をしなければなりません。ConEmuとかを使っていない場合、WSLのターミナル画面はコマンドプロンプトやPowerShellと同じWindowsのコンソール機能を使っています。このコンソール自体は複数の文字コードに対応しています。ウィンドウのバーを右クリックして「プロパティ」を開いてくみてください。「オプション」タグに「現在のコードページ」の所に現在のコードページ(Windowsで文字コードを識別するための番号)が表示されていることでしょう。日本語環境であれば、932 (ANSI/OEM - 日本語 Shift-JIS)
か65001 (UTF-8)
の何れかです。Windows標準のコマンドプロンプトやPowerShellは932になります(chcpコマンドで切り替え可能)が、WSL上のUbuntuを立ち上げたときは自動的に65001に切り替わります。これは932なコマンドプロンプト上でbash
とした場合も同様です。先程、WSL上のUbuntuはUTF-8になっていると言いましたが、このコンソール機能でもUTF-8となっているため、日本語のメッセージなどが文字化けせずに表示されるようになっています。
話を戻しましょう。UbuntuではC++上の"~"
という文字列リテラルはUTF-8である。UbuntuのターミナルもUTF-8である。であれば、文字化けするのはおかしいはずです。では何がいけなかったのか?それはソースコードの文字コードとコンパイラが認識する文字コードが一致していなかったからです。
ちょっとだけ変えた下のコードを同じようにShift_JISで保存してコンパイルしようとしてみてください。
C++
1#include<iostream>
2using namespace std;
3
4int main()
5{
6 cout<<"サンプル表"<<endl;
7 cout<<"Example"<<endl;
8
9 return 0;
10}
エラーになりましたね?このコードがShift_JISであった場合、UTF-8として解釈すると文法エラーになってしまいます。「表」という文字はShift_JISで95 5Cです。これをUTF-8で解釈すると5Cが\
と解釈され、後ろに続く"
をエスケープしてしまい、文字列が終了せず、改行まで進んでしまうからです。どうしたらこれを防げるのか…というと、実は先程の話と同じです。ソースコードがどの文字コードであるとして読み込むかは、環境のデフォルトまたはコンパイラオプションで決定されます。ほとんどの場合は、先程と同じものがデフォルトになります。
では、上のコードをコンパイル出来るようにオプションを加えてみましょう。次のような形で-finput-charset=cp932
オプションを加えてみてください(sjisとかでもできそうな気がするのですが、手元の環境ではcp932以外だとうまくいきませんでした)。
g++ -finput-charset=cp932 {{ソースコードファイル名}}
うまくコンパイル出来ましたね。実際に実行してみてください。おぉっと、文字化けしませんでしたね。ソースコードはShfit_JISで、環境はUTF-8のはずです。なのにうまくいきました。何故なんでしょうか?
ここまでちゃんと読んでいればわかるはずです。ソースコードの文字コードとコンパイルによって出力されたバイナリに埋め込まれた文字コードは関係が無いと言うことです。それぞれ別々に管理されており、環境のデフォルトまたはコンパイラオプションによって決定されます。コンパイル後の文字コードは、GCCであれば-fexec-charset
で指定可能です。このオプションはそれぞれバラバラに設定でき、文字コードの変換が行われることになります。
さて、最初の文字化けの話に戻ります。文字化けしたのはShift_JISのコードをUTF-8として解釈したからでした。この処理では、UTF-8として解釈できる文字はそのまま、解釈できない文字は�
REPLACEMENT CHARACTER(U+FFFD)に置き換えて、バイナリに埋め込みます(この置き換えはiconvの動作に依存したものと思われます)。そして実行される環境もUTF-8であるため、解釈できた一部の文字Tvと解釈できずに置き換わられ�が表示されるというわけです。
文字化けしないようにする方法は次の二つです。
- ソースコードの文字コードをコンパイル時に指定する。
- 環境のデフォルトにソースコードの文字コードを合わせる。
最初のは既に示しましたが、毎回毎回オプションを付けるのは面倒ですので、2.の方法、つまり、UTF-8として保存する方が良いでしょう。
なお、Visual Studioでもコンパイルしたいとか、そういった移植性のあるコードを書きたい場合はまた別の話です。解説するのが嫌になるぐらいめんどくさい話になるので、移植性をとるならソースコードはASCIIのみにするのが一番楽です。
【おまけ】
C++11からu8"~"
という表現ができるようになりました。このu8付きの文字列リテラルは、環境やコンパイルオプションによらず、バイナリ上ではUTF-8として存在することになります。例えば、Windows日本語環境において、各文字列リテラルは次のようになります。
C++
1"これはWindows-31J";
2u8"これはUTF-8";
3L"これはUTF-16"; // Windowsでのワイド文字列はUTF-16を採用している
4u"これはUTF-16";
5U"これはUTF-32";
※ ソースコードの文字コードとコンパイラに認識している文字コードが一緒であれば、ソースコードの文字コードに関係無く、こうなります。
"~"
とu8"~"
の文字コードが異なるのはWindowsぐらいしかありませんが、コマンドプロンプトで表示させるエラーメッセージはWindows-31Jだけど内部での各テキストの扱いは全てUTF-8にしたいという時に便利かも知れません。
参考: 文字列リテラル - cppreference.com