デフォルトではシフトJIS
Visual StudioでVisual C++の空のプロジェクトを作成し、下記のようなソースを書いて実行してみます。
#include<stdio.h> #include<string.h> int main(void) { char s[] = "あいうえお"; int len = strlen(s); printf("%s\n", s); printf("length = %d\n", len); for (int i = 0; i < len; i++) { unsigned char b = (unsigned char)s[i]; printf("%02X ", b); } printf("\n"); return 0; }
【実行結果】
あいうえお length = 10 82 A0 82 A2 82 A4 82 A6 82 A8
日本語文字が1文字2バイトで扱われており、'あ' に対応するコードが 82A0 であることから、シフトJISであることがわかります。また、ソースファイルを秀丸やVSCodeのような文字コードを確認できるテキストエディタで開いてみると、シフトJISであることがわかります。そして、コンソール上に正しく ”あいうえお” と表示できていることからコンソール出力もシフトJISであることがわかります。
つまり、デフォルトではソースファイル、実行バイナリ、コンソール出力ともシフトJISになっています。
ソースファイルをUTF-8で保存
秀丸などのテキストエディタでソースファイルをUTF-8で保存しなおします。するとビルドエラーが発生します。どうも Visual Studio はデフォルトではBOM無しのUTF-8を自動認識できないようです。BOM付きUTF-8で保存してやると、ビルドが成功します。ところが実行結果を見ると先ほどと変化がなく、あいかわらずシフトJISになっています。つまり、ソースファイルをUTF-8にしてもビルド時にはシフトJISに変換されるようです。
実行バイナリをUTF-8でビルド
Visual Studioでプロジェクトの「プロパティ」→「構成プロパティ」→「C/C++」→「コマンドライン」で「追加のオプション」に下記を設定すると、ソースファイルも実行バイナリもUTF-8で扱われます。こうすればソースファイルはBOM無しのUTF-8でも大丈夫です。
/utf-8
【実行結果】
縺ゅ>縺・∴縺・ length = 15 E3 81 82 E3 81 84 E3 81 86 E3 81 88 E3 81 8A
日本語文字が1文字3バイトで扱われており、'あ' に対応するコードが E38182 であることから、UTF-8であることがわかります。いっぽうで、コンソール出力は文字化けしており、特徴的な '縺' (シフトJISで E381) が出現しています。このことからコンソール出力は依然としてシフトJISであることがわかります。
コンソールをUTF-8に設定
コンソール出力をUTF-8に設定するには、下記のように SetConsoleOutputCP関数を使います。
#include<Windows.h> #include<stdio.h> #include<string.h> int main(void) { SetConsoleOutputCP(CP_UTF8); char s[] = "あいうえお"; (以下同文)
【実行結果】
あいうえお length = 15 E3 81 82 E3 81 84 E3 81 86 E3 81 88 E3 81 8A
ワイド文字 (UTF-16) で扱う
ここまでは文字をchar型で扱ってきました。char型では日本語文字はマルチバイト文字として扱われ、シフトJISでは1文字2バイト、UTF-8では1文字3バイトになります(※)。strlen関数は単純に終端文字までのバイト数を数えるので、日本語の文字数を正しく数えることはできません。
これに対し、wchar_t 型は1文字2バイトのワイド文字を扱います。wchar_t 型では英数字も日本語文字も等しく1文字2バイトになります(※)。
※ 絵文字や特殊な漢字など、ごく一部の文字は1文字4バイトになりますが、この記事では扱いません。
#include <stdio.h> #include <wchar.h> #include <locale.h> int main() { setlocale(LC_ALL, ""); wchar_t ws[] = L"あいうえお"; int len = wcslen(ws); wprintf(L"%ls\n", ws); printf("length = %d\n", len); for (int i = 0; i < len; i++) { unsigned short w = (unsigned short)ws[i]; printf("%04X ", w); } printf("\n"); return 0; }
【実行結果】
あいうえお length = 5 3042 3044 3046 3048 304A
1文字2バイトで扱われており、'あ' に対応するコードが 3042 であることから、UTF-16であることがわかります。また、wcslen関数は文字数を正しく数えています。L"あいうえお" のようにワイド文字列リテラルには L を付けることに注意してください。ソースファイルがシフトJISであるかUTF-8であるかによらず、ワイド文字リテラルはビルド時にUTF-16に変換されます。
TCHAR型とは?
TCHAR型はUnicode環境(ワイド文字対応環境)とUnicode非対応環境とのソース互換のためにある型で、ビルド時にwchar_t型かchar型かのいずれかになります。_tcsnlen関数などの_tcs系関数は、ビルド時にwcs系関数かstr系関数かになります。Unicode環境で下記のソースをビルドすると、実行結果は前述の wchar_t を使った場合と同じになります。
#include <stdio.h> #include <tchar.h> #include <locale.h> int main() { setlocale(LC_ALL, ""); TCHAR ts[] = _T("あいうえお"); int len = _tcslen(ts); _tprintf(_T("%s\n"), ts); printf("length = %d\n", len); for (int i = 0; i < len; i++) { unsigned short w = (unsigned short)ts[i]; printf("%04X ", w); } printf("\n"); return 0; }