パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
文字コード

文字コードとはコンピュータ上で各文字に割り当てられるバイト表現、もしくはバイト表現と文字の対応関係を指す。

その他の外部情報

本サイトでの解釈

パソコンが日本語を実用的に扱えるようになって久しいが、プログラミングでは頭痛の種であり続ける。本項目は必要と思われる情報をまとめるが、日本語アプリケーションをどのように開発するかは別項目で考察する。

本サイトは以下の文字コード(符号化文字集合)を考慮するが、各実装が正確に規格準拠する訳ではない。またこの表が日本語を表現できる文字コードの全てを網羅しているわけでもない。C++プログラミングが符号(文字を表現するバイト表現)を格納するのに通常用いる型も示すが、ウィンドウズユニックスライクでwchar_tが異なる。C++20でchar8_tが追加されたが、本サイトはC++17準拠で記述しているため括弧付きとした。

分類 名称 規格 文字集合(概要) 符号単位 ウィンドウズ ユニックスライク 符号長/文字 ステートフル
非ユニコード ASCII ANSI INCITS 4 英数字記号+制御文字 7ビット char char 1
ANK JIS X0201 英数字記号+制御文字+カタカナ 8ビット char char 1
シフトJIS JIS X0208 附属書1 [半角] 英数字記号+制御文字+カタカナ
[全角] 英数字記号+カタカナ+ひらがな+第1~2水準漢字+その他
8ビット char char 1~2
JIS JIS X0208 附属書2 [ASCII] 英数字記号+制御文字
[JIS漢字] 英数字記号+カタカナ+ひらがな+第1~2水準漢字+その他
7ビット char char 1~2
ユニコード UTF-8 ISO/IEC 10646 世界で使われる全ての文字 8ビット char, (char8_t) char, (char8_t) 1~4
UTF-16 16ビット wchar_t, char16_t char16_t 1~2
UTF-32 32ビット char32_t wchar_t, char32_t 1
UCS-2 ユニコード基本多言語面 16ビット wchar_t, char16_t char16_t 1

本項目で"文字"と"符号点(コードポイント)"は1対1に対応可能と仮定し、例えば正確には"符号長/符号点"と言うところを"符号長/文字"と表す。この仮定はユニコードの合成済み文字(precomposed character)で成立せず、例えばÄはU+00C4(1符号点)あるいはU+0041とU+0308の合成(2符号点)である。

非ユニコード

英語以外のいくつかの言語(主にヨーロッパ言語)はASCIIを8ビット拡張して表現できる(ISO/IEC 8859-1~16)。ANKもそういった拡張の一つと見なせるがISO/IEC 8859には属さない。これら全てASCIIに上位互換するが、相互は拡張部の符号が重なり互換しない。なおANKはASCIIバックスラッシュの符号0x5Cに円マークを割り当て厳密にはASCII上位互換でない。漢字由来の言語(中国語、日本語、韓国語)は1文字を一つの8ビット符号で表現できず複数の符号を必要とする。

シフトJISとJISはJIS X0208の附属書で規格化される。X0208本体は7ビットまたは8ビット符号単位で、2符号長の漢字などと空白文字を含む1符号長制御文字を定義する。7/8ビットの差異は制御文字集合だけに現れる。"JIS漢字"はX0208の定義する2符号長文字(漢字など)集合を指すが、X0208本体を指す場合もあるため後者は"JIS漢字(規格)"として区別する。JIS漢字(規格)においてJIS漢字と制御文字の符号は重複せず混在できるが、JIS漢字とASCII/ANKは符号が重なり混在できない。

名称 JIS規格 文字集合(概要) 符号単位 ウィンドウズ ユニックスライク 符号長/文字
JIS漢字(規格) X0208 [制御文字] 空白文字+制御文字 7/8ビット char char 1
[JIS漢字] 英数字記号+カタカナ+ひらがな+第1~2水準漢字+その他 7ビット char char 2

シフトJISはANK文字集合とJIS漢字を合わせて規格化する。JIS漢字にJIS漢字(規格)と異なる符号を用いて第一符号をASCII/ANKの外に置き第二符号と組み合わせて1文字を表現する。第二符号はANKと重なるがアプリケーションは第一符号から判別できる。シフトJIS対応可能なアプリケーションはANKも区別なく処理できる。ANK文字集合とJIS漢字で重なる文字集合(英数字記号+カタカナ)は別定義と扱う。ANK文字の等間隔フォント幅をJIS漢字の半分とすれば文字列の等間隔フォント表示長と符号長が整合するため、各々の文字字形はそのように定めるのが通例となっている。前者文字を"半角文字"、後者文字を"全角文字"と呼び習わす。

名称 JIS規格 文字集合(概要) 符号単位 ウィンドウズ ユニックスライク 符号長/文字
シフトJIS X0208 附属書1 [半角] 英数字記号+制御文字+カタカナ 8ビット char char 1
[全角] 英数字記号+カタカナ+ひらがな+第1~2水準漢字+その他 8ビット char char 2

JISは主にASCII文字集合とJIS漢字を合わせて規格化する。ASCII、ANK、JIS漢字(規格)をエスケープシーケンス指示で切り替えるステートフル(状態を持つ)な文字コードで、シフトJIS同様にASCII/ANKとJIS漢字で重なる文字を別定義と扱う事が多い。ANKの8ビット文字(カタカナ)は禁止されるが許容する実装が多く、そういった文字列を7ビット前提のアプリケーションが処理すればエラーとなる。JISは"ISO-2022-JP"とも呼ばれるがISO/IEC 2022に適合しているわけではない。

名称 JIS規格 文字集合(概要) 指示 符号単位 ウィンドウズ ユニックスライク 符号長/文字 備考
JIS
または
ISO-2022-JP
X0208 附属書2 [ASCII] 英数字記号+制御文字 ESC ( B 7ビット char char 1 デフォルト
[ANK] 英数字記号+制御文字(+カタカナ) ESC ( J 7(8)ビット 1 カタカナは規格外
[JIS漢字] 英数字記号+カタカナ+ひらがな+第1~2水準漢字+その他 ESC $ B 7ビット 2 JIS X0208
ESC $ @ JIS C6226

ユニコード

ユニコードにはUTF-7(符号単位7ビット)も存在するが通常使用されないので本サイトは考慮しない。以下にUTF-8、UTF-16、UTF-32をまとめるが、ウィンドウズでは規格に準拠しない慣用が多い。

名称 文字集合 符号単位 符号長/文字 規格 細分 BOM エンディアン
UTF-8 全て 8ビット 1~4 準拠 UTF-8 無し -
非準拠 UTF-8(慣用) U+FEFF -
UTF-8N(慣用) 無し -
UTF-16 全て 16ビット 1~2 準拠 UTF-16 U+FEFF BOMから判断
無し ビッグエンディアン
UTF-16BE 無し ビッグエンディアン
UTF-16LE 無し リトルエンディアン
非準拠 UTF-16(慣用) 無し リトルエンディアン
UTF-16BE(慣用) U+FEFF ビッグエンディアン
UTF-16LE(慣用) U+FEFF リトルエンディアン
UTF-32 全て 32ビット 1 準拠 UTF-32 U+FEFF BOMから判断
無し ビッグエンディアン
UTF-32BE 無し ビッグエンディアン
UTF-32LE 無し リトルエンディアン

UTF-16とUTF-32はバイト列がエンディアンに依存する。BOM(バイトオーダーマーク)は先頭に付加される特殊文字で、アプリケーションはBOMからエンディアンを知ることができる。あるいはUTF-16BEなどの名称でエンディアンは明示される。BOM付加も名称明示も無い場合はビッグエンディアンとされるが、ウィンドウズアプリケーションの多くはデフォルトでリトルエンディアンとする。BOM付加と名称明示は規格上排他とされるが、両方を備える慣用も多い。UTF-8はエンディアンの影響を受けないが、BOMを付加する慣用が存在する。そのような慣用ではBOMを付加しない本来のUTF-8をUTF-8Nと呼ぶ。BOM(U+FEFF)はユニコード制御文字の一つであるZWNBSP(ゼロ幅ノーブレークスペース)と符号点が一致するが、問題となることはまず無い。BOMが付加される場合のバイト列は以下となる。

名称 符号単位 エンディアン BOM(U+FEFF)バイト列
UTF-8 8ビット - EF BB BF
UTF-16 16ビット ビッグエンディアン FE FF
リトルエンディアン FF FE
UTF-32 32ビット ビッグエンディアン 00 00 FE FF
リトルエンディアン FF FE 00 00

UTF-8の1符号長文字によるサブセットはASCIIと一致する。通常の日本語文字は3符号長でシフトJISより大きい。"半角英数字記号"は1符号長である一方、"半角カタカナ"は3符号長となる。UTF-16の1符号長文字によるサブセットがUCS-2で、文字集合はユニコード基本多言語面である。通常の日本語文字は基本多言語面に属し1符号長である。UTF-32は全て1符号長文字で、UTF-32とUCS-4は同義になる(The Unicode Standard Version 8.0 C.2 Encoding Forms in ISO/IEC 10646)。

名称 符号単位 1符号長サブセット 通常の日本語文字
UTF-8 8ビット ASCII (英数字記号+制御文字) 3符号長 (24ビット)
UTF-16 16ビット UCS-2 (基本多言語面) 1符号長 (16ビット)
UTF-32 32ビット UCS-4 (全ての文字) 1符号長 (32ビット)

ウィンドウズAPI

ウィンドウズの文字コードは規格に沿わない場合が多く用語も誤用を含むが、広く普及し(少なくともウィンドウズ世界の中では)デファクトスタンダード化している。ウィンドウズAPIは"コードページ"と"ユニコード"と呼ばれる二つの手法で文字コードを扱うが、コードページ(手法)は古くユニコード(手法)が推奨される。

コードページ(手法)は歴史的背景から"ANSIコードページ"とも呼ばれるが誤用で、ANSI(米国国家規格協会)にコードページ規格は無く扱われる文字コードも今やほとんどANSI規格外にある。コードページ(手法)は整数値を用いて複数の文字コードを識別するが、この整数値も"コードページ"と呼ばれる。コードページ(手法)の扱う文字コードは符号単位8ビットでASCII上位互換に限定される。この制約を満たすならばシフトJISのような可変符号長も扱うことができる。UTF-8でさえこれを満たしコードページ(整数値)が割り当てられている。

ウィンドウズはシステムロケールがコードページ(手法)を採るアプリケーションの実行されるコードページ(整数値)を決定する。日本版ウィンドウズのシステムロケールのデフォルトは当然日本語だが、ウィンドウズスタートメニューから[設定|時刻と言語|地域|日付、時刻、地域の追加設定|地域|管理|システムロケールの変更]で変更できる。コードページ(整数値)は日本語で932(シフトJIS)、英語(米国)で1252(ISO/IEC 8859-1)であり、アプリケーションの期待するコードページ(整数値)がシステムロケールと一致しないと文字化けを起こす。

ユニコード(手法)は文字コードとして符号単位16ビットとしたユニコードを使う。ウィンドウズは当初コードページ(手法)のみだったが、ウィンドウズNTでユニコード(手法)が追加されUCS-2を採用する。当初これで世界で使われる全ての文字を表現できるとしていたものの、結果として不十分でウィンドウズ2000でUTF-16へ拡張される。しかしUCS-2文字集合(基本多言語面)が主要言語のほとんどの場合をカバーできるためか、今日に至っても多くのアプリケーションがUTF-16に完全対応しない。例えば😀😁😂😃をエクセルへコピペしてLENワークシート関数で文字数を調べて欲しい(少なくともエクセル2013は4でなく8を返す)。ユニコード(手法)を採るアプリケーションはシステムロケールに影響されない。

文字列を扱うウィンドウズAPI関数のほとんどが"コードページバージョン"と"ユニコードバージョン"を用意する。例えばMessageBoxAとMessageBoxWで、関数名末尾のA(コードページバージョン)とW(ユニコードバージョン)で識別する。C++プログラミングではマクロUNICODEによりマクロMessageBoxの定義を切り替え、適切にtypedefされたTCHARを文字型とすることで、ソースコード共通化が図られている。

#ifdef UNICODE
...
typedef wchar_t TCHAR;
...
#define MessageBox MessageBoxW
...
#else
...
typedef char TCHAR;
...
#define MessageBox MessageBoxA
...
#endif

C++規格はwchar_tのサイズを1符号で全てのサポートする文字を表現できるものとして定義する(JTC1/SC22/WG21 N4659 6.9.1/p5)。ウィンドウズのwchar_tは16ビットでUTF-16符号に用いられるが、基本多言語面以外の文字に2符号を必要とし規格に沿わない。一方、ユニックスライクのwchar_tは32ビットでUTF-32符号に用いられ規格に沿う。例えばユニックスライクは標準ライブラリ関数wcslenの戻り値が実際の文字数を表すと安全に仮定できるが、ウィンドウズはそうならない。

コンパイラの文字コード

ソースコードは物理ファイルの入力文字集合で記述され、コンパイラのソース文字集合に変換されてから翻訳される。C++規格による翻訳フェーズを概観する(JTC1/SC22/WG21 N4659 5.2/p1)。ソースコードを基本ソース文字集合(5.3/p1)にマップし、マップできない文字をISO/IEC 10646(ユニコード)のユニバーサル文字名(5.3/p2)に置換する(フェーズ1)。いくつかの処理(フェーズ2~3)の後、#includeを含むプリプロセッサディレクトリを処理する(フェーズ4)。フェーズ4ではインクルードファイル毎にフェーズ1~4が繰り返される。文字/文字列リテラルはプレフィクスに従い適切な実行文字集合の符号/符号配列へ変換される(フェーズ5)。残りの処理を行い(フェーズ6~9)完了する。

以下に文字/文字列リテラルの変換をまとめる(5.13.3、5.13.5)。文字リテラルは1符号長文字に限られる。総称としての実行文字集合(5.2/p1フェーズ5)と通常の文字/文字列リテラルが変換される実行文字集合(5.13.3/p2、5.13.5/p6)を区別している。文字型char16_t、char32_tとプレフィクスu8、u、Uは曖昧なwchar_tへの対策としてC++11で導入されたが、ウィンドウズAPI(ユニコードバージョン)はwchar_tのままにある。C++20が導入したchar8_tは括弧付きで表示した。

リテラル N4659 実行文字集合(総称) 符号化
文字 'a' 5.13.3/p2 実行文字集合 実装依存 char
u8'a' 5.13.3/p3 ISO/IEC 10646 UTF-8 char (char8_t)
u'a' 5.13.3/p4 ISO/IEC 10646 UTF-16 char16_t
U'a' 5.13.3/p5 ISO/IEC 10646 UTF-32 char32_t
L'a' 5.13.3/p6 ワイド実行文字集合 実装依存 wchar_t
文字列 "asdf" 5.13.5/p6 実行文字集合 実装依存 const char [ ]
u8"asdf" 5.13.5/p7 ISO/IEC 10646 UTF-8 const char [ ] (const char8_t [ ])
u"asdf" 5.13.5/p10 ISO/IEC 10646 UTF-16 const char16_t [ ]
U"asdf" 5.13.5/p11 ISO/IEC 10646 UTF-32 const char32_t [ ]
L"asdf" 5.13.5/p12 ワイド実行文字集合 実装依存 const wchar_t [ ]
覚え書き
u'a'あるいはU'a'の符号化は1符号長ながらUTF-16あるいはUTF-32である事を規格は明示するが(5.13.3/p4-5)、u"asdf"あるいはU"asdf"の符号化がUTF-16あるいはUTF-32であることを規格は明示していないように見える(5.13.5/p10-11)。

本サイト使用のコンパイラmingw-w64は入力文字コード(符号化された入力文字集合)、実行文字コード(符号化された実行文字集合)、ワイド実行文字コード(符号化されたワイド実行文字集合)をオプション設定する(GCC 13.2.0 Manual 3.13 Options Controlling the Preprocessor)。マニュアルはロケールが得られる場合はそれを入力文字コードのデフォルトとするとしているが、日本語ウィンドウズ環境ではロケールが得られない場合のデフォルトUTF-8のままで、これを環境変数LC_ALLやLANGで変更する事もできない。

文字コード オプション デフォルト 日本語プログラミングでの選択肢
入力文字コード -finput-charset UTF-8 UTF-8, CP932
実行文字コード -fexec-charset UTF-8 UTF-8, CP932
ワイド実行文字コード -fwide-exec-charset UTF-16 UTF-16

mingw-w64はiconvライブラリを文字コードの変換に用いる。mingw-w64のソース文字コード(符号化されたソース文字集合)はUTF-8なので、入力文字コードは必ずUTF-8へ変換される。iconvで利用できる文字コード名はMSYS2のPOSIX互換ターミナルで確認できる。

$ iconv -l

CP932はシフトJISのiconvにおける文字コード名の一つである。シフトJISの文字コードには他にSHIFT-JISとその別名(MS_KANJI、SHIFT_JIS、SJIS、CSSHIFTJIS)があるが、これらは使用できない。符号0x5Cの解釈が両者で異なり、CP932はASCIIに従いバックスラッシュ、SHIFT-JISはANKに従い円マークである。SHIFT-JISを入力文字コードとするとバックスラッシュ(0x5C)はソース文字コード(UTF-8)の円マークに置換されコンパイルエラーを起こす。シフトJISでバックスラッシュ(0x5C)を含むテキストファイルを作成し、以下の2出力を比較してほしい。

$ iconv -f CP932 -t UTF-8 test_file.txt > test_file_from_CP932_to_UTF-8.txt
$ iconv -f SHIFT_JIS -t UTF-8 test_file.txt > test_file_from_SHIFT-JIS_to_UTF-8.txt

実行文字コード、ワイド実行文字コードはリテラルの文字型に制約される。入力文字コードはそういった制約は無くmingw-w64ならiconvの扱える文字コードであれば良い。ただし翻訳フェーズ4が示すように入力文字コードはインクルードファイルへも使用するので、例えばUTF-16とするとASCIIファイル(ライブラリインクルードは通常ASCIIで記述される)をインクルードできずコンパイルエラーを発生する。入力文字コードUTF-8とCP932はASCIIファイルを安全にインクルードするが、UTF-8はCP932ファイルをインクルードできず反対もまた同様である。例えば本サイトはMSYS2の導入するiconvライブラリを利用するが、インクルードファイルiconv.hはコメントにASCII外のUTF-8符号(U+2018、LEFT SINGLE QUOTATION MARKとU+2019、RIGHT SINGLE QUOTATION MARK)を含み入力文字コードCP932ではインクルードに失敗する。

日本語による変数名

C++の識別子にユニバーサル文字名を含むことができる(JTC1/SC22/WG21 N4659 5.10/p1)。例えば変数名や関数名を日本語文字列にできるし、入力文字コードが日本語文字を表現できるなら変数名や関数名として日本語文字列を直接書き込める。残念ながらmingw-w64では未だ完全に実現されておらずユニバーサル文字名のみが使用できる。

#include <iostream>
int main()
{
// Although a Japanese character can be used in an identifier with its
// universal character name, the character itself cannot be. That does not
// conform to the standard completely.
// auto 変数=int{0};
// std::cout << "変数" << 変数 << std::endl;
auto \u5909\u6570=int{0};
std::cout<<"変数=\u5909\u6570="<<\u5909\u6570<<std::endl;
// An ASCII character cannot be used in an identifier with its universal
// character name. That conforms to the standard.
// auto \u0076\u0061\u0072\u0069\u0061\u0062\u006C\u0065=int{0};
auto variable=int{0};
std::cout<<"variable=\u0076\u0061\u0072\u0069\u0061\u0062\u006C\u0065="<<variable<<std::endl;;
return 0;
}