0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-12-08

はじめに

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

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

開発課題

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

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

テスト用プログラム

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

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

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

TEST-WCHAR-SJIS.C(ファイルの文字コードは Shift-JIS)
#include <stdio.h>
#include <locale.h>
int	main() {
	setlocale( LC_ALL, "" );
	_putws( L"表示" );
	return 0;
}
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++ では(当たり前だが)四つのプログラム全てが無事にコンパイルでき,いずれも正しく動作する。

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

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

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

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

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

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

別解

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

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 と指定したほうが短くて楽だからである。

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

まとめ

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

A案)既存のコードを流用する場合(あまり修正したくない場合)

  • main() 関数の冒頭で setlocale( LC_ALL, ".utf-8" ) 関数を呼び出す。
  • ファイルの文字コードを UTF-8 で保存する。
  • Visual C++ でコンパイルするときはオプション -utf-8 を付ける。

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

B案)新規に作成する場合(修正を厭わない場合)

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

C案)新規に作成する場合(英語に不自由しない人)

  • 入出力する文字列を全て英文で書き直す。※そもそも日本語を使わなければ良い。

D案)国際化対応を考える人,プロ向け?

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

おまけ

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

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

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?