読者です 読者をやめる 読者になる 読者になる

Visual C++(cl.exe)で UTF-8 のファイルをコンパイルする

日本語版 Windows では、デフォルトの文字コードとして Shift-JIS(CP932)が広く使われています。
この Shift-JIS、2バイト目が ASCII コードと同じ、という文字がいくつか存在します。

例えば「ソ」の Shift-JIS でのコードは16進数で835cで、2バイト目の5cバックスラッシュ(または¥マーク)と同じです。
このため、例えば

#include <iostream>
int main()
{
    char str[] = "ソ";
    std::cout << str << std::endl;
    return 0;
}

上記を Shift-JIS で保存してコンパイルしてみます。
Visual Studio では問題なくコンパイル・実行できるのですが、gcc(g++)では、

error: missing terminating " character
     char str[] = "▒\";
                  ^

怒られました。
「ソ」の2バイト目と、それに続くダブルクォーテーションがエスケープシーケンスとして処理されてしまうため、文字列の終わりを表すはずのダブルクォーテーションが文字列に含まれてしまい、文字列が終わっていないというエラーになってしまうわけです。

Shift-JIS に対応していないツールでは、似たような問題はしょっちゅう顕在化します。
いわゆる「ダメ文字」問題ですね。
「ダメ文字」でググるといろいろ情報が出てくると思います。


てことで本題。
ファイルの文字コードに Shift-JIS なんて使うのはやめて、UTF-8 を使うようにしよう!

さっきのコードを UTF-8 *1 で保存して、Visual Stuidio(cl.exe)でコンパイルして実行してみます。
コンパイルは通りますが、実行すると、

繧ス

文字化けしました

上のコードのコンパイル時には特に何も言わませんが、文字列の部分を、

    char str[] = "あいうえお";

こう書き換えてコンパイルしてみると、

warning C4819: ファイルは、現在のコード ページ (932) で表示できない文字を含んでいます。データの損失を防ぐために、ファイルを Unicode 形式で保存してください。
error C2001: 定数が 2 行目に続いています。
error C2143: 構文エラー: ';' が 'std::cout' の前にありません。

なんかいろいろ怒られました

どうやら、ソースの文字コードが実際には UTF-8 であるにも関わらず、Shift-JIS としてコンパイルされているみたいですね。
UTF-8 での「お」の最後のバイトと、それに続くダブルクォーテーションが、Shift-JIS として解釈されることにより、合体して1文字になってしまっているのでしょう。


さて、どうしたものか。
こそっと脚注入れましたが、上の例は UTF-8 BOM なしのファイルで試した結果です。
BOM ありの UTF-8 なら、エラーも文字化けも起きません
Visual StudioUTF-8 のファイルを扱う場合、BOM ありにするのがもっとも単純な解決法のようです。

しかし、単なるテキストに過ぎないソースファイルにバイナリコードを含めるのはやっぱり何かヘンな気がしますし、BOM 付きだとうまく動かないツールもあったりするなど、単に BOM を付ければ万事解決、というわけにはいかないとも思うのです。
てなわけで、もうちょっと調べてみました。


要は コンパイラにソースの文字コードを伝えられればいいわけですよね。
cl.exe の場合は、BOM がこの役割を担っているわけですが、こういうのってオプションで指定できることが多いものです。
MS製ツールは/?オプションでヘルプが見れますので、cl /?の出力を眺めてみます。

そしたらこんなオプションを見つけました。 *2

/source-charset:|.nnnn は、ソース文字セットを設定します
/execution-charset:|.nnnn は、実行文字セットを設定します
/utf-8 は、ソースおよび実行文字セットを UTF-8 に設定します
/validate-charset[-] では、有効な文字に対してのみ UTF-8 ファイルが検証されます

MSDN にも記事がありました(2017年3月4日現在、日本語のものはないようです)。

-source-charset (Set Source Character Set)
-execution-charset (Set Execution Character Set)
-utf-8 (Set Source and Executable character sets to UTF-8)
-validate-charset (Validate for compatible characters)

記事の対象バージョンは Visual Studio 2015 となっています。
Visual Studio 2013 以前のバージョンにはこれらのオプションは存在しません。


4つのそれっぽいオプションのうち、今回の目的に合致しそうなのは、/source-charset:utf-8です。これは、ソースファイルの文字コードを指定します。

  • /execution-charset:utf-8は、指定すると、コンパイルされたプログラムの出力が UTF-8 になります。コマンドプロンプトは デフォルトでは CP932 で動いており、UTF-8 での出力は文字化けするため、今回はこのオプションは使えません。コマンドプロンプト上でchcp 65001を実行することで UTF-8 で動くようにはできますが、この時のフォントに日本語フォントの指定ができない *3 ため、表示するものが無くて結局文字化けします
  • /utr-8は短くて便利かと思いきや、/execution-charset:utf-8が指定されてしまうため使えません。
  • /validate-charsetは、ソース中の文字が UTF-8 として表すことができるかどうかをチェックします。上3つのいずれかを指定すれば、勝手に指定されたことになります。特に気にする必要はなさそうです。


てことでオプション指定してコンパイル

cl /source-charset:utf-8 source.cpp

これで、BOM なし UTF-8 でもコンパイルでき、文字化けも起こりません

なお、Visual Studio のプロジェクトの設定には該当する項目がありません。
IDE でこれらオプションを設定するには、プロジェクトのプロパティから [C/C++] → [コマンドライン] → [追加のオプション] に指定します。


ということで、方針としては、

  • BOM ありで不都合が無ければ、BOM ありの UTF-8 を使う
  • BOM を付けたくない場合、Visual Studio 2015 以降なら/source-charsetが使える
  • 2013 より前ならおとなしく BOM 付けるか Shift-JIS に甘んじるか……

こんな感じになると思います。


当初は BOM ありにすりゃおk、ってことを備忘録として書いておこうと思ってたんですが、オプションの存在を知りましたので、これも合わせて記事にしてみたら、予想外に長くなってしまいました。

文字列めんどくさい(´・ω・`)

*1:BOMなし

*2:cl /? の出力が非常に多い(およそ200行)おかげで最初は気が付かず、それっぽいワードでググって MSDN の該当ページを先に発見(;´∀`)

*3:レジストリいじればできるらしい