clang-clで日本語を扱うと色々面倒

# はじめに

筆者はこれまで長らく **Windows** 上で開発するときの C コンパイラとしては **Microsoft** の **Visual C++** を愛用してきたが，先日 **LLVM** の **clang-cl** の出来栄え（最適化性能のことね）が素晴らしいことを実感した。

- [自炊の画像形式をPNGからWebPに移行する２～WebPユーティリティを評判のClangでビルドする - Qiita](https://qiita.com/TETSURO1999/items/239a359f337b2211ac04)
- [自炊の画像形式をPNGからWebPに移行する３～WebPユーティリティを最強Intel C++コンパイラでビルドする - Qiita](https://qiita.com/TETSURO1999/items/2f9a2e2f6a732cdb25d9)

ところが別のプログラムで日本語文字列を用いたソースコードを **clang-cl** でコンパイルしようとすると大量の警告やエラーメッセージで表示されて辟易する羽目に陥った。**clang-cl** に移行するための最大の障害であろうか。

# 開発課題

ソースコードを **Visual C++** と **clang-cl** で共用し，かつ同一の環境で実行した場合にいずれも正常に日本語文字列を出力できること。

**Visual C++** および **clang-cl** は **Visual Studio 2022** 搭載版のものとする。

# テスト用プログラム

ファイルの文字コードを **Shift-JIS** と **UTF-8**（**BOM** 付き）の二種類，文字列を `char` 型と `wchar_t` 型の二種類で合計四種類用意した。「表」の文字コードは **Shift-JIS** では **95h**, **5Ch** と表され，2 バイト目の **5Ch** がバックスラッシュ記号 `\` に当たるため，**Shift-JIS** に対応していない処理系だとエスケープ文字と間違えて文字化けを引き起こすはずだ。

```c:TEST-CHAR-SJIS.C（ファイルの文字コードは Shift-JIS）
#include <stdio.h>
int	main() {
	puts( "表示" );
	return 0;
}
```

```c:TEST-CHAR-UTF8.C（ファイルの文字コードは UTF-8 BOM 付き）
#include <stdio.h>
int	main() {
	puts( "表示" );
	return 0;
}
```

ワイド文字列を出力する場合には最初に `setlocale` を呼び出す必要がある。

```c:TEST-WCHAR-SJIS.C（ファイルの文字コードは Shift-JIS）
#include <stdio.h>
#include <locale.h>
int	main() {
	setlocale( LC_ALL, "" );
	_putws( L"表示" );
	return 0;
}
```

```c:TEST-WCHAR-UTF8.C（ファイルの文字コードは UTF-8 BOM 付き）
#include <stdio.h>
#include <locale.h>
int	main() {
	setlocale( LC_ALL, "" );
	_putws( L"表示" );
	return 0;
}
```

# Microsoft Visual C++ によるコンパイルと実行

**Visual C++** では（当たり前だが）四つのプログラム全てが無事にコンパイルでき，いずれも正しく動作する。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  padding 0 16 16 16
  roundCorner 16
  backgroundColor #202020
  horizontalAlignment left
  fontStyle normal
  fontSize 14
  fontName monospaced
  fontColor white
}
</style>
sprite TITLE <svg width="640" height="32">
<path d="M0 0 v28 a4 4 0 0 0 4 4 h292 a4 4 0 0 0 4 -4 v-28 h-300" fill="#606060" />
<text x="8" y="22" font-size="16" fill="#white">Visual C++ によるコンパイルと実行例</text>
</svg>
title
<$TITLE>

c:\Qiita>cl -nologo TEST-CHAR-SJIS.C
TEST-CHAR-SJIS.C

c:\Qiita>TEST-CHAR-SJIS
表示

c:\Qiita>cl -nologo TEST-CHAR-UTF8.C
TEST-CHAR-UTF8.C

c:\Qiita>TEST-CHAR-UTF8
表示

c:\Qiita>cl -nologo TEST-WCHAR-SJIS.C
TEST-WCHAR-SJIS.C

c:\Qiita>TEST-WCHAR-SJIS
表示

c:\Qiita>cl -nologo TEST-WCHAR-UTF8.C
TEST-WCHAR-UTF8.C

c:\Qiita>TEST-WCHAR-UTF8
表示
end title
@enduml
```

# LLVM clang-cl によるコンパイルと実行

一方，**clang-cl** の場合，ファイルの文字コードが **Shift-JIS** だと警告やエラーが発生する。ファイルの文字コードを **UTF-8** にすれば警告は出なくなるが，文字列が `char` 型だと正常に動作しない。ただし，これはコンソール（コマンドプロンプト）のコードページがデフォルトの **932** の場合である。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  padding 0 16 16 16
  roundCorner 16
  backgroundColor #202020
  horizontalAlignment left
  fontStyle normal
  fontSize 14
  fontName monospaced
  fontColor white
}
</style>
sprite TITLE <svg width="640" height="32">
<path d="M0 0 v28 a4 4 0 0 0 4 4 h264 a4 4 0 0 0 4 -4 v-28 h-272" fill="#606060" />
<text x="8" y="22" font-size="16" fill="white">clang-cl によるコンパイルと実行例</text>
</svg>
sprite ERROR1 <svg width="320" height="32">
<path d="M0 0 v32 h256 v-32" fill="#606060" />
<text x="8" y="22" font-size="16" fill="white">大量の警告メッセージと文字化け</text>
</svg>
sprite ERROR2 <svg width="320" height="32">
<path d="M0 0 v32 h272 v-32" fill="#606060" />
<text x="8" y="22" font-size="16" fill="white">コンパイルには成功したが文字化け</text>
</svg>
sprite ERROR3 <svg width="320" height="32">
<path d="M0 0 v32 h288 v-32" fill="#606060" />
<text x="8" y="22" font-size="16" fill="white">コンパイルに失敗したため実行できず</text>
</svg>
sprite SUCCESS <svg width="320" height="32">
<path d="M0 0 v32 h112 v-32" fill="#606060" />
<text x="8" y="22" font-size="16" fill="white">ようやく成功</text>
</svg>
title
<$TITLE>

c:\Qiita>clang-cl TEST-CHAR-SJIS.C
TEST-CHAR-SJIS.C(3,9): <color:magenta>warning:</color> illegal character encoding in string literal
[-Winvalid-source-encoding]
    3 |         puts( "<color:lime><95></color>\<color:lime><8E><A6></color>" );
      |                <color:lime>^∼∼∼</color>
TEST-CHAR-SJIS.C(3,10): <color:magenta>warning:</color> unknown escape sequence '\xFFFFFF8E'
[-Wunknown-escape-sequence]
    3 |         puts( "<color:lime><95></color>\<color:lime><8E><A6></color>" );
      |                    <color:lime>^∼∼∼∼</color>
TEST-CHAR-SJIS.C(3,12): <color:magenta>warning:</color> illegal character encoding in string literal
[-Winvalid-source-encoding]
    3 |         puts( "<color:lime><95></color>\<color:lime><8E><A6></color>" );
      |                         <color:lime>^∼∼∼</color>
3 warnings generated.

c:\Qiita>TEST-CHAR-SJIS
侮ｦ

<$ERROR1>

c:\Qiita>clang-cl TEST-CHAR-UTF8.C

c:\Qiita>TEST-CHAR-UTF8
陦ｨ遉ｺ

<$ERROR2>

c:\Qiita>clang-cl TEST-WCHAR-SJIS.C
TEST-WCHAR-SJIS.C(5,12): <color:red>error:</color> illegal character encoding in string literal
    5 |         _putws( L"<color:lime><95></color>\<color:lime><8E><A6></color>" );
      |                   <color:lime>^∼∼∼</color>
TEST-WCHAR-SJIS.C(5,13): <color:magenta>warning:</color> unknown escape sequence '\xFFFFFF8E'
[-Wunknown-escape-sequence]
    5 |         _putws( L"<color:lime><95></color>\<color:lime><8E><A6></color>" );
      |                       <color:lime>^∼∼∼∼</color>
TEST-WCHAR-SJIS.C(5,15): <color:red>error:</color> illegal character encoding in string literal
    5 |         _putws( L"<color:lime><95></color>\<color:lime><8E><A6></color>" );
      |                            <color:lime>^∼∼∼</color>
1 warning and 2 errors generated.

<$ERROR3>

c:\Qiita>clang-cl TEST-WCHAR-UTF8.C

c:\Qiita>TEST-WCHAR-UTF8
表示

<$SUCCESS>
end title
@enduml
```

結果を表にまとめると以下のようになる。

<table>
<caption>表１ clang-cl によるコンパイルと実行結果一覧</caption>
<thead>
<tr><th>ファイル名</th>
    <th>ファイルの文字コード</th>
    <th>文字列の型</th>
    <th>コンパイル</th>
    <th>実行</th></tr>
</thead>
<tbody>
<tr></tr>
<tr><th>TEST-CHAR-SJIS</th>
    <td>Shift-JIS</td>
    <td>char</td>
    <td>警告</td>
    <td>文字化け</tr></tr>
<tr></tr>
<tr><th>TEST-CHAR-UTF8</th>
    <td>UTF-8</td>
    <td>char</td>
    <td>成功</td>
    <td>文字化け</tr></tr>
<tr></tr>
<tr><th>TEST-WCHAR-SJIS</th>
    <td>Shift-JIS</td>
    <td>wchar_t</td>
    <td>エラー</td>
    <td>実行不可</tr></tr>
<tr></tr>
<tr><th>TEST-WCHAR-UTF8</th>
    <td>UTF-8</td>
    <td>wchar_t</td>
    <td>成功</td>
    <td>正常</tr></tr>
</tbody>
</table>

プログラム `TEST-CHAR-UTF8` はコードページを **UTF-8** に変更してやれば正常に動作する。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  padding 0 16 16 16
  roundCorner 16
  backgroundColor #202020
  horizontalAlignment left
  fontStyle normal
  fontSize 14
  fontName monospaced
  fontColor white
}
</style>
sprite TITLE <svg width="640" height="32">
<path d="M0 0 v28 a4 4 0 0 0 4 4 h288 a4 4 0 0 0 4 -4 v-28 h-296" fill="#606060" />
<text x="8" y="22" font-size="16" fill="white">コードページを UTF-8 に変更した場合</text>
</svg>
title
<$TITLE>

c:\Qiita>chcp 65001
Active code page: 65001

c:\Qiita>TEST-CHAR-UTF8
表示
end title
@enduml
```

ただし，コードページを **UTF-8** に変更すると，今度は **Visual C++** でビルドした場合に動かくなってしまうのだ。

# 別解

**Windows 10 バージョン 1803** 以降では，ユニバーサル C ランタイムで **UTF-8** コード ページの使用がサポートされた。これを利用するためには `setlocale` を使用するときに、コード ページとして `.utf-8` を指定すればよい。

```c:TEST-CHAR-UTF8a.C（ファイルの文字コードは UTF-8 BOM 付き）
#include <stdio.h>
#include <locale.h>
int	main() {
	setlocale( LC_ALL, ".utf-8" );
	puts( "表示" );
	return 0;
}
```

この場合，**Visual C++** ではコンパイルオプションとして `-utf-8` を付ける。オプション `-utf-8` は下記の二つのオプションを指定したのと等価である。

- オプション `-source-charset:utf-8` ソース文字セットを **UTF-8** にする
- オプション `-execution-charset:utf-8` 実行文字セットを **UTF-8** にする

ファイルに **BOM** があればソース文字セットが **UTF-8** であることは明確なので実行文字セットを **UTF-8** にするオプション `-execution-charset:utf-8` だけで良いが，単にオプション `-utf-8` と指定したほうが短くて楽だからである。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  padding 0 16 16 16
  roundCorner 16
  backgroundColor #202020
  horizontalAlignment left
  fontStyle normal
  fontSize 14
  fontName monospaced
  fontColor white
}
</style>
sprite TITLE <svg width="640" height="32">
<path d="M0 0 v28 a4 4 0 0 0 4 4 h292 a4 4 0 0 0 4 -4 v-28 h-300" fill="#606060" />
<text x="8" y="22" font-size="16" fill="#white">Visual C++ によるコンパイルと実行例</text>
</svg>
title
<$TITLE>

c:\Qiita>cl -nologo -utf-8 TEST-CHAR-UTF8a.C
TEST-CHAR-UTF8a.C

c:\Qiita>TEST-CHAR-UTF8a
表示
end title
@enduml
```

一方，**clang-cl** のほうは何も指定しなくてよい。デフォルトで `-utf-8` が指定されているからだ。むしろ，それを解除できないと考えたほうがよい。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  padding 0 16 16 16
  roundCorner 16
  backgroundColor #202020
  horizontalAlignment left
  fontStyle normal
  fontSize 14
  fontName monospaced
  fontColor white
}
</style>
sprite TITLE <svg width="640" height="32">
<path d="M0 0 v28 a4 4 0 0 0 4 4 h264 a4 4 0 0 0 4 -4 v-28 h-272" fill="#606060" />
<text x="8" y="22" font-size="16" fill="#white">clang-cl によるコンパイルと実行例</text>
</svg>
title
<$TITLE>

c:\Qiita>clang-cl TEST-CHAR-UTF8a.C

c:\Qiita>TEST-CHAR-UTF8a
表示
end title
@enduml
```

# まとめ

結局のところ，ソースファイルの文字コードとコンパイルされた実行ファイルの文字コードは別物なのだ。さらに，コンソールのコードページも意識する必要がある。

### Ａ案）既存のコードを流用する場合（あまり修正したくない場合）
- `main()` 関数の冒頭で `setlocale( LC_ALL, ".utf-8" )` 関数を呼び出す。
- ファイルの文字コードを **UTF-8** で保存する。
- **Visual C++** でコンパイルするときはオプション `-utf-8` を付ける。

:::note
`char` 型の文字列に **UTF-8** 文字列を格納する場合でも，適切なコードページを指定すればユニバーサル C ランタイム関数や，末尾に **A** がつく **Win32 API** にそのまま文字列を引き渡すことができるようになったようだ。※**Windows10 バージョン 1903** 以降
:::

### Ｂ案）新規に作成する場合（修正を厭わない場合）
- 入出力する文字列を `wchar_t` 型で書き直す。
- 入出力関数も `wchar_t` 型に対応したものに変更する。
- `main()` 関数の冒頭で `setlocale( LC_ALL, "" )` 関数を呼び出す。
- ファイルの文字コードを **UTF-8** で保存する。できれば **BOM** を付ける。**BOM** を付けない場合，**Visual C++** でコンパイルするときはオプション `-source-charset:utf-8` を付ける。

###  Ｃ案）新規に作成する場合（英語に不自由しない人）
- 入出力する文字列を全て英文で書き直す。※そもそも日本語を使わなければ良い。

### Ｄ案）国際化対応を考える人，プロ向け？

- 日本語文字列はリソースファイルとして外出しする。※ワイはやらんけど。

# おまけ

Ａ案の場合，もしも新規で作るのなら **UTF-8** 文字列であることを明示するため **C11** で追加された新しい文字列リテラル指定 **u8** を使ったほうが良いかもしれない。

```c:TEST-CHAR-UTF8b.C
#include <stdio.h>
#include <locale.h>
int	main() {
	setlocale( LC_ALL, ".utf-8" );
	puts( u8"表示" );
	return 0;
}
```

# 参考文献

- [/utf-8 (ソースおよび実行文字セットを UTF-8 に設定する) - microsoft](https://learn.microsoft.com/ja-jp/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8)

- [setlocale, _wsetlocale - microsoft](https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/setlocale-wsetlocale)

- [Clang command line argument reference - LLVM](https://clang.llvm.org/docs/ClangCommandLineReference.html)

- [C++(Visual Studio)でUTF-8を扱うための試行錯誤のメモ - Qiita](https://qiita.com/BlueSilverCat/items/1e5af7fdaeaf8fd2289d)

- [文字列はファイル依存？コンパイラ依存？ - Qiita](https://qiita.com/mm_sys/items/7b8b415a54d1cd0caa5e)

- [UTF-8文字列をAPI引数で使えるようになった - すらりん日記](https://blog.techlab-xe.net/using-string-utf-8-winapi/)