文字化けとsetlocale関数

コンソールでの文字化け

ソースファイルや実行ファイルの文字コードを変更すると、例えば以下のような単純なプログラムでも文字化けが発生することがあります。


#include <stdio.h>

int main()
{
	printf("あいうえお");
	getchar();
}

Visual Studio(Windows)を初期設定のまま使用している場合は、上記コードで文字化けは発生しません。
gcc(Linux)でも、ソースファイルの文字コードにUTF-8を使用し、その他の設定を特に変更指定していなければ文字化けは起こりません。
(ただし、正常に日本語が表示可能な環境である場合)

文字化けというのは入力側の文字コードと処理側(出力)の文字コードとが一致していないため起こります。
(もちろんデータが壊れているときにも起こり得ます)
Windowsのコンソール(コマンドプロンプト)は送られてきた文字をShift_JISとして処理しますが、ここにUTF-8などの別の文字コードの文字を送ると文字化けが起こります。
Linuxのコンソール(ターミナル)はUTF-8が標準で、ここにShift_JISなどを表示しようとすると文字化けが起こります。

実行ファイルをシステムが使用する文字コードと同じにする

文字化けを解決するには、実行ファイルの文字コードをシステムが使用するものと同じに設定するのが最も簡単です。
つまりWindowsならばShift_JIS、LinuxならUTF-8です。
初期状態でこの設定になっているはずなので、余計な変更をしなければ文字化けは起こりません。
(ただし古いLinux環境ではEUC-JPがデフォルトの文字コードになっていることがあります)

コンパイラの文字コードの設定についてはVisual Studioの文字コードをUTF-8にするおよびgccの文字コードを変更するを参照してください。

これらのコンパイラは実行ファイルの文字コードに合わせてソースコード上の文字データを変換します。
例えば実行ファイルにShift_JISを指定すると、ひらがなの「あ」は「82 A0」というバイト列(Shift_JISでの「あ」)になります。
これはWindowsコンソールで正常に「あ」が表示されます。
UTF-8を指定すると「E3 81 82」というバイト列(UTF-8での「あ」)になります。
これはLinuxコンソールで正常に「あ」が表示されます。

「E3 81 82」のバイト列をWindowsコンソールで表示すると、Shift_JISとして解釈され、わけのわからない文字が指定されるため文字化けが発生します。

なお、ソースファイルの文字コードはプログラムの実行時には関係ありませんが、コンパイラの入力設定と合っていないとコンパイル時に適切に変換されないため文字化けが発生します。

Windowsコンソールの文字コードを変える

Windowsの場合、コンソールが使用する文字コードをUTF-8に変えることでも正常に文字を表示することができます。
ただしこの方法はお勧めしません。

コンソール上で「chcp」というコマンドを実行すると、現在の文字コード(コードページ)を表示することができます。

C:\>chcp
現在のコード ページ: 932

C:\>sample.exe
縺ゅ>縺・∴縺・

C:\>chcp 65001
Active code page: 65001

C:\>sample.exe
あいうえお

C:\>chcp 932
現在のコード ページ: 932

実際にはコードページを切り替えると画面の表示はクリアされます。

「932」というのはShift_JISを表す番号です。

「chcp 65001」と入力して実行するとコンソールの文字コードがUTF-8に切り替わります。
「65001」はUTF-8を表す番号です。
Shift_JISに戻したい場合は「chcp 932」を入力します。

コードページを65001に切り替えた上で、この画面からプログラムを起動すればUTF-8の文字を正常に表示することができます。

ただしこの変更は現在開いているウィンドウでのみ有効で、コンソールを終了すると元に戻ります。
C言語上からこれを操作する方法はありません。

なので、Visual Studio上からビルド実行する場合には使えません。
ビルド後に、生成された実行ファイルをあらかじめ開いておいたコンソール画面から実行する、などの手順が必要になります。
この場合はデバッグ機能が使えません。

Windowsの設定を変更することでコンソールの標準の文字コードを変更することはできます。
しかしそれをすると他のコンソールアプリが文字化けする可能性が高いです。
(Shift_JISで正常に表示できるように作られているプログラムが大半なため)
自分専用プログラムならまだ良いですが、他人に使用してもらうような場合はわざわざコンソールの文字コードを変更してもらう必要があるため、あまりお勧めできる方法ではありません。

setlocale関数

C言語にはsetlocaleという関数があります。
これを使用することでも、Windowsではコンソールの文字化けに対処できます。

setlocale関数は、ロケールという国や地域ごとに異なる表現を設定するものです。
例えば通貨の単位や日付の形式などで、文字コードもこれに関係します。
ここでは文字コードの話に絞ります。

setlocale関数の使用には<locale.h>のインクルードが必要です。
まずはサンプルコードを以下に示します。


//実行ファイルの文字コードはUTF-8とする

#include <stdio.h>
#include <locale.h> //setlocale関数の実行に必要

int main()
{
	//現在のロケールを取得
	//設定は何も変更されない
	char* locale = setlocale(LC_ALL, NULL);
	printf("current locale: %s\n", locale);

	printf("abcあいうえお\n");

	//文字コードをUTF-8に変更
	locale = setlocale(LC_ALL, "ja_JP.UTF-8");
	printf("current locale: %s\n", locale);

	printf("abcあいうえお\n");

	getchar();
}
char* setlocale(
  int category,
  const char* locale
)
categoryで指定したカテゴリのロケールをlocaleに設定する。
戻り値は設定したロケールを表す文字列。
ロケールの設定に失敗した場合はNULLを返す。
localeにNULLを与えた場合はロケールを変更せず、現在設定されているロケールを返す。
localeに空文字を与えた場合はシステム既定のロケールに設定する。

以下の実行結果はWindowsで実行ファイルにUTF-8を指定した場合です。

current locale: C
abc縺ゅ>縺・∴縺・
current locale: ja_JP.UTF-8
abcあいうえお

最初の「あいうえお」はUTF-8文字列をそのまま表示しようとしています。
WindowsコンソールはShift_JISなのでこれは文字化けしてしまいます。
(半角英数文字は互換性があるため正常に表示できます)

setlocale関数は、第二引数にNULLを指定すると現在使用しているロケールを取得できます。
(変更はしません)
「C」というのはプログラム起動直後に自動的に設定されるロケールで「Cロケール」というものです。
この状態では半角の英数記号(ASCII文字)が正常に使用できます。
(その他、通貨や数の表記などがアメリカ式になります)

CロケールではUTF-8をコンソールに表示できないので、ロケールを変更します。
第二引数にロケール文字列を指定すると、現在のロケールを変更できます。
具体的に指定できる文字列は後述しますが、上のサンプルコードではUTF-8を指定しています。
設定の変更後は日本語が正常に表示できています。

Windowsのコンソールは、現在のロケールからShift_JISへ文字コードの変換を行います。
(ただし現在のロケールがCロケールの場合は変換しない)
つまりこの場合はUTF-8からShift_JISへの変換が行われるため、正常に表示できるようになります。

Linuxコンソールはこのような変換は行いません。

第一引数

説明が前後しましたが、第一引数は以下の定数を指定できます。
(環境によってはこれ以外にもあります)

LC_ALL
全てのロケール
LC_COLLATE
strcoll関数とstrxfrm関数の挙動に影響
LC_CTYPE
文字処理関数の挙動に影響
LC_MONETARY
通貨の表示
LC_NUMERIC
数値の表示
LC_TIME
日時の表示

文字コードの設定の場合はLC_CTYPEか、全てを一括で設定するLC_ALLを指定します。

LC_CTYPEを変更することにより、文字処理関数の挙動が変化します。
文字の処理は使用している文字コードによって内部動作を変える必要があるので、これで設定できるわけです。

上記サンプルコードでは文字処理関数を使用していませんが、上述した通りWindowsのコンソールは画面表示の際に文字の変換処理を行います。
これの挙動もLC_CTYPEの影響を受けるため、適切に設定することで文字化けが回避できます。

第二引数

第二引数はロケールを設定する文字列です。
文字列は「言語_地域.文字コード」の設定で、以下の四パターンの設定が可能です。


//言語_地域.文字コード
setlocale(LC_ALL, "ja_JP.utf8");

//言語のみ
setlocale(LC_ALL, "ja");

//言語_地域
setlocale(LC_ALL, "ja_JP");

//文字コードのみ
setlocale(LC_ALL, ".utf8");

また、NULLや空文字の指定は特別な意味を持ちます。
NULLを指定すると現在設定されているロケール文字列を取得します。
設定は変更されません。
空文字を指定するとシステム既定のロケールに設定されます。


char *locale;

//現在のロケールを取得
//ロケールの変更はしない
locale = setlocale(LC_ALL, NULL);

//システム既定のロケールに設定
locale = setlocale(LC_ALL, "");

システム既定のロケールとは、例えば日本語Windowsでは「Japanese_Japan.932」などです。
(「932」はコードページ932のことで、Shift_JISのことです)

第二引数に具体的に指定できる文字列はシステムにより異なります。
Windows環境で指定できる言語_地域の文字列は以下に情報があります。
Country-Region Strings | Microsoft Docs
コードページは以下に情報があります
Code Page Identifiers - Win32 apps | Microsoft Docs

上記には載っていませんが、日本語版WindowsではJapaneseJapanese_Japanおよび省略形のja-JPja_JPも利用可能です。

ただ、言語_地域の指定と文字コードの指定とを組み合わせた場合に上手く設定できないことがあるようです。
また、「.utf-8」は有効でも「.shift_jis」は受け付けないなど、いまいち一貫性に欠けます。
設定に失敗した場合の戻り値はNULLなので、配布用などで使用できる文字列が不明な場合、設定できなかった時のためにいくつかの候補を用意しても良いでしょう。


char *locale;
//色々なShift_JISを表す文字列を設定してみる
//どれかがNULL以外を返せば成功
if ((locale = setlocale(LC_ALL, "ja_JP.sjis")) ||
	(locale = setlocale(LC_ALL, "ja_JP.SJIS")) ||
	(locale = setlocale(LC_ALL, "ja_JP.shift_jis")) ||
	(locale = setlocale(LC_ALL, "ja_JP.Shift_JIS")) ||
	(locale = setlocale(LC_ALL, "ja_JP.932")) ||
	(locale = setlocale(LC_ALL, "Japanese_Japan.932")))
{
	printf("current locale: %s\n", locale);
}
else
{
	printf("setlocale failed.");
}

//設定ファイルを用意して
//ユーザーに設定してもらうのも良い

Linuxの場合はターミナルから「locale -a」コマンドを実行すれば使用可能なロケールの一覧が表示されます。


$locale -a
C
C.UTF-8
POSIX
ja_JP.SJIS
ja_JP.utf8
japanese.sjis

Windowsでsetlocale関数にUTF-8が指定できるようになったのは「Windows 10 ビルド 17134(2018年4月の更新プログラム)」以降です。
それ以前のバージョンではUTF-8は設定できません。

おまけ:Clangを使用する場合

コンパイラのひとつである「Clang」は入力も出力もUTF-8にのみ対応しており、Visual Studioやgccのようにコンパイルオプションで文字コードを切り替える機能はありません。
コンパイル時に文字を変換する機能もなく、文字データはソースファイル上のバイト列がそのまま実行ファイル内で使用されます。

そのため、ソースファイルにはWindows上ではShift_JISを、Linux上ではUTF-8を使用すれば文字化けは起こりません。
ただしUTF-8以外の文字コードを読み込んだ場合はコンパイル時に警告がでます。
(無視しても動作します)

5C問題

ClangはShift_JISに正式に対応していないため、Windows上では問題が発生する場合があります。

C言語では「\」記号はエスケープ文字に使用されます。
\記号は1バイト文字(半角)で、Shift_JIS(やASCII)では「5C」番(10進数表記で92番)に文字が割り当てられています。

「\」記号は環境により円記号またはバックスラッシュのどちらかで表示されます。

Shift_JISで全角文字(日本語など)は2バイト文字です。
この2バイト文字には、2バイト目が「5C」番となる文字が多数存在します。
代表的なものは「ソ(83 5C)」「十(8F 5C)」「表(95 5C)」「暴(96 5C)」などです。
これらの文字の2バイト目がエスケープ文字と誤って判断されてしまい、正常に表示ができないことがあります。
(ちなみに1バイト目が「5C」となる2バイト文字はShift_JISにはない)

これらの文字はダメ文字などと呼ばれます。
Visual StudioやgccなどはShift_JISに対応しているため問題は起こりません。
しかしClangは文字列をそのままのバイト列として処理するため、文字化けが起こります。

以下はソースファイルにShift_JISを使用し、Clangでコンパイルした場合です。


#include <stdio.h>

int main()
{
	//「表」がダメ文字
	printf("表示\n");
	getchar();
}
侮ヲ

「表示」はバイト列に直すと「95 5C 8E A6」となります。
これは2バイト目の「5C」が次の「8E」をエスケープする物と解釈されてしまいます。
「5C 8E」に該当するエスケープ文字はなく、この場合は「5C」は単純に無視されます。
つまり「95 8E A6」というバイト列を文字として表示しようとします。
その結果が「侮ヲ」という文字列になります。

ダメ文字は文字化け以外にも以下のような問題を引き起こします。


//ダメ文字のダメな例
//ダメ文字にはすべて「表」を使用している

//1、文字列の末尾のダメ文字
//文法エラーになりコンパイルできない
char str[] = "結果発表";

//2、コメント行の末尾のダメ文字
//次の行までコメントとみなされてしまう
//なので以下のprintf関数は実行されない

//結果発表
printf("あいうえお");
  1. "結果発表"という文字列は末尾のダメ文字が次の"記号をエスケープしてしまいます。
    それにより文字列の終端記号が無くなってしまうため、文法エラーになります。
  2. 行の終端には改行文字が存在しますが、コメント末尾のダメ文字はこの改行文字をエスケープしてしまいます。
    それにより改行文字が無くなってしまうため、次の行の末尾までがコメント行と認識されてしまいます。

対策

ダメ文字の解決策は、ダメ文字の直後に\記号を挿入することです。


#include <stdio.h>

int main()
{
	//ダメ文字対策

	printf("表\示");

	char str[] = "結果発表\";

	//結果発表\
	printf("%s\n", "あいうえお");

	getchar();
}

ダメ文字である「表」の直後に\記号を書くことで、エスケープ文字の\\にすることができます。
「表\」は「95 5C 5C」というバイト列になり、「5C 5C」は\記号を表すエスケープ文字ですから、結果として「95 5C」を表すことができます。

コメント行末尾のダメ文字に関しては、次の行もコメント行にすることでも回避できます。


#include <stdio.h>

int main()
{
	//結果発表
	//←このコメント行を消すと動作しない
	printf("%s\n", "あいうえお");

	getchar();
}

ClangでソースファイルにShift_JISを使用していて、原因のよくわからないコンパイルエラーやおかしな挙動が発生した場合はダメ文字を疑ってみるのもいいかもしれません。
以下にダメ文字の一覧を示します。

ソ噂浬欺圭構蚕十申曾箪貼能表暴予禄兔喀媾彌拿杤歃濬畚秉綵臀藹觸軆鐔饅鷭