2011/2/3 - 2011/3/5
Pen Jr.D言語なんてやってみそ脱素人文字化け

文字化け

配列は、

import std.stdio, std.conv;

void main(){
  // 文字列リテラルは immutable( char )[]
  auto s = cast( char[] )"Welcome Beginner";
  /+
  int[] sii = s;   // これはダメ
  +/
  auto si = cast( int[] )s;
  auto ss = cast( ushort[] )s;
  auto sb =
    cast( ubyte[] )si[ 0 .. 2 ];  // 長いからちょん切る
  
  writeln(
    "s = ", s,
    "\nsb = ", sb,
    "\nss = ", ss,
    "\nsi = ", si,
    // 16進数表記で
    "\n\ncast( ubyte )s[ 0 ] = 0x",
      to!string( cast( ubyte )s[ 0 ], 16 ),
    "\nsb[ 0 ] = 0x",
      to!string( sb[ 0 ], 16 ),
    "\nsb[ 1 ]sb[ 0 ] = 0x",
      to!string( sb[ 1 ], 16 ), to!string( sb[ 0 ], 16 ),
    "\nss[ 0 ] = 0x",
      to!string( ss[ 0 ], 16 ),
    "\nss[ 1 ]ss[ 0 ] = 0x",
      to!string( ss[ 1 ], 16 ), to!string( ss[ 0 ], 16 ),
    "\nsi[ 0 ] = 0x",
      to!string( si[ 0 ], 16 )
    );
  
  // 型の確認
  pragma(
    msg,
    "s : ", typeof( s ),
    "\nsi : ", typeof( si ),
    "\nss : ", typeof( ss ),
    "\nsb : ", typeof( sb )
    );
}
D:\Pen-Jr\My Programs\types>dmd test
s : char[]
si : int[]
ss : ushort[]
sb : ubyte[]

D:\Pen-Jr\My Programs\types>test
s = Welcome Beginner
sb = [87, 101, 108, 99, 111, 109, 101, 32]
ss = [25943, 25452, 28015, 8293, 25922, 26983, 28270, 29285]
si = [1668048215, 543518063, 1768383810, 1919250030]

cast( ubyte )s[ 0 ] = 0x57
sb[ 0 ] = 0x57
sb[ 1 ]sb[ 0 ] = 0x6557
ss[ 0 ] = 0x6557
ss[ 1 ]ss[ 0 ] = 0x636C6557
si[ 0 ] = 0x636C6557

値は同じで、扱いが変わるだけだったりします。

記憶クラスは、

import std.stdio;

void main(){
  // string は immutable( char )[]
  string s = "Welcome Beginner";
  /+
  // 参照型 の immutable は暗黙変換では外せない
  char[] cc = s;  // これは叱られる
  +/
  auto c = cast( char[] )s;
  writeln( "s = ", s );
  /+
  s[ 0 ] = 'A';   // これもダメ
  +/
  c[ 0 ] = 'A';   // 書き換え出来ちゃう!
  c[ 7 ] = '%';
  c[ 15 ] = 'X';
  
  // 動的配列は参照型だからね
  writeln( "\ns = ", s, "\nc = ", c );
}
参照型

実行すると!

D:\Pen-Jr\My Programs\types>test
s = Welcome Beginner

s = Aelcome%BeginneX
c = Aelcome%BeginneX

思惑通りですが、これは 未定義動作 です。

string( 動的配列 )は 参照型 なので、元の immutable なデータの書き換えを コンパイラ が許します。

ほとんどうまくいきますが、未定義動作 なので、いつ失敗するかわかりません。

文字化け
D言語の文字コードは全て Unicode ですが、Windows の日本語環境は Shift-JIS が基本です。

よくわかってないと、まずコンパイル通りませんし文字を扱うライブラリでもことごとくエラーになります。

そして言う通り全部 Unicode にすると今度はプログラム実行時に Windows で文字化けします。

Win32API は Unicode(UTF-16)
Win32API が Shift-JIS というのは間違えで(デフォルトですが)、「互換性のため残された」という解釈がされるべきだと思います。

Unicode を使うと、16ビットの OS には非対応になりますが、互換性の切り捨てはその程度問題で「弱者切り捨て」には該当しませし「本来必須項目であるべき」とも思います。

16ビット対応を考えなければ Win32API には ANSI でしか使えないものはありません( ANSI では使えないことはある)から、Unicode を使えば、0 終端にするだけで済みますし、リテラルなど多くは 0終端になっていますから簡単で、文字化けなどとは無縁になります。

時代は Unicode
Unicode の文字化け問題は、何も D言語特有のものではありません。

また「各種文字コードへの対応」というのは、安易なものでは "変換" という無駄なコストを無意識にかけてしまい勝ちで、無駄なコストを極力避けるためには、開発環境全てをその文字コード対応にする必要があります。

早い話 Unicode 使っとけば OK
さすがに WindowsXP 以前の OS への対応はもう良いでしょうから、GUI アプリならば、Unicode で問題ありませんし、コンソールアプリでの日本語表示は考えなくても大丈夫かと思います。
D言語
Shift-JIS のコンパイル
日本語 Windows 環境では、メモ帳などほとんどのテキストエディターはデフォルトで Shift-JIS でから、何も知らなければソースファイルも Shift-JIS で保存されます。
import std.stdio;

void main(){
  writeln( "Welcome" );
}

このコードは Shift-JIS で保存していても、コンパイルが無事終了します。

  // 日本語
import std.stdio;

void main(){
  writeln( "Welcome" );
}
test.s で保存。

これをコンパイルすると

D:\Pen-Jr\My Programs\Tests>dmd test
test.d(1): invalid UTF-8 sequence
test.d(1): invalid UTF-8 sequence
test.d(1): invalid UTF-8 sequence
test.d(1): invalid UTF-8 sequence
test.d(1): invalid UTF-8 sequence
文字コード(エンコード)
普段から意識することはないでしょう。

また、「 文字コード を意識できないとプログラムを組めない」なんてことはなく、言語・環境によってはそこそこの経験者でも、「 文字コード なんて気にしたことねーよ」なんて人も多分いるでしょう。

文字コードとは
文字でも何でも、CPU から見れば数値です。

試しに文字コードを覗いてみる

import  std.windows.charset,
        std.c.string, std.stdio;

enum : string{
  S1 = "abcABC012!@#",
  S2 = "D言語の世界"
}

  // 配列なら何でも? 16進表示する
void disp( T )( T[] str ){
  writef( "%s = [\n  ", str );  // %s は文字列表示
  int wd = 7;
  foreach( i, n; ( cast( ubyte[] )str )[] ){
    if( i ) write( ", " );
    // ちょっと見やすくする
    if( i > wd ){
      write( "\n  " );
      wd += 8;
    }
    writef( "0x%02x", n );   // 16進表示
  }
  writeln( "\n  ]" );
}

  // UTF8 を Shift-JIS に変換して string を返す
auto toMBS( in string s ){
  return to!( string )( toMBSz( s ) ); 
}

void main(){
  writeln( "---- UTF8 ----" );
  disp( S1 );
  writeln( "---- Shift-JIS ----" );
  disp( toMBS( S1 ) );
  writeln( "---- UTF8 ----" );
  disp( S2 );
  writeln( "---- Shift-JIS ----" );
  disp( toMBS( S2 ) );
}

実行結果

---- UTF8 ----
abcABC012!@# = [
  0x61, 0x62, 0x63, 0x41, 0x42, 0x43, 0x30, 0x31,
  0x32, 0x21, 0x40, 0x23
  ]
---- Shift-JIS ----
abcABC012!@# = [
  0x61, 0x62, 0x63, 0x41, 0x42, 0x43, 0x30, 0x31,
  0x32, 0x21, 0x40, 0x23
  ]
---- UTF8 ----
D險€隱槭・荳也阜 = [
  0x44, 0xe8, 0xa8, 0x80, 0xe8, 0xaa, 0x9e, 0xe3,
  0x81, 0xae, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c
  ]
---- Shift-JIS ----
D言語の世界 = [
  0x44, 0x8c, 0xbe, 0x8c, 0xea, 0x82, 0xcc, 0x90,
  0xa2, 0x8a, 0x45
  ]
※ アルファベットや記号は同じ

※ UTF8の日本語は文字化けする

英数字など以外が Shift-JIS と UTF8 では全く違う数値になりますから、コマンドプロンプトで日本語を出力する場合などは、Unicode から Shift-JIS へ変換しなければ日本語の部分が正しく表示されません。

文字コードの問題 <<・・
プログラム言語の場合、文字コードの問題は二つあります。
コンパイラ解析時の問題
コンパイラは主に英数字や記号の記述を解析しますから、どれが記号で英数字だかわからなければお話になりません。

これは、開発環境の問題ですから、ソースコードの記述(構文)とは関係ありません。統合環境では意識する必要は無いでしょうが、コンパイラとテキストエディターを別々に使う時にはエディターの設定をそのコンパイラ(言語)の指定に通りにする必要がありますから、"エンコードの種類" などで指定を簡単に変えられないエディターでは面倒な場合があります。

プログラム実行時の問題
言語の指示通りの文字コードをコンパイラに渡すことが出来ても、アプリケーションの入出力の問題が自動解決されるとは限りません。ファイルの入出力、コンソールの入出力等々は "OS" や実行時にやり取りをするアプリケーションやライブラリとの問題です。

他から受け取る文字コードの種類を知らないと、その文字を正しく解析できません(偶然を除く)し、相手に正しい文字コードで渡すことが出来なければ間違って解析されたり、間違った文字が表示(俗称文字化け)されたりします。

アスキーコード( ASCII )

英数字及び記号が割り振られています。基本的な英語キーボードは ASCIIコード に割り当てられて文字(制御文字も含めて)が入力できるようになっている・・・はずです。

ASCIIコード は歴史も古く、世界標準と言って間違いないでしょうが、1バイトコード(0 ~ 0xFF)ですから、そこに世界中の文字を割り振ることは出来ませんが、ASCIIコード が賢かったのは、8ビットではなく 7ビットコード(0 ~ 0x7F)にしていたことです。拡張を考慮していたのこどうかは知りません(元々はパリティビットと言ってエラーチェックのために使われていたらしいです)が、0 ~ 0x7F だけが予約されていて、0x80 以降を使って拡張可能になっています。

そして、ほとんどのプログラム言語のプログラムコードはこの ASCII文字 だけで記述できます。また、普通に考えれば ASCIIコード 以外の文字コードは未知の文字コードとして扱って問題なく処理できるはずなのですが、世の中不思議です。

Unicode
一つの文字コードで世界中全ての文字を扱うことが考慮されたものですから、Unicode を指定しておけば、それぞれの言語に対応するためのコストを抑えることができます。また、Unicode が世界標準規格となれば、利用者が文字コードを意識する必要は最小になると思います。
UTF-8
8ビット(バイト)単位の可変長文字コードで、ASCIIに対する 正しい上位互換

ASCII文字(英数字・記号)などは1バイトでそれ以外(漢字など)は2バイト以上(バイト(8ビット)単位の可変長)。

正しい上位互換なので、ASCIIコードの処理だけをすればそれ以外は未知の文字コードとして扱っても、当たり前に安全な処理が出来ます。

英数字などは1バイトなので、英語圏に棲むに人たちにとっては無駄が無く精神衛生上良いと思われます。また、プログラムコードなど英数字が多いものも同様ですが、漢字などはほとんど3バイト以上なので、日本語の文書などは無駄が多くなるとも言えます。ただし、今時テキストデータの容量などどちらにしても取るに足らないものでしょうから、"精神衛生上の問題" でしかないと思います。

ほとんどのプログラム言語の場合、ソースの文字コードは UTF-8 と考えておけば今後も問題が起きづらく、また、文字を処理するプログラムも 文字コード指定を UTF-8 にすれば、最少のコストで安全な処理コードを組みやすくなるはずです。

UTF-16
世界中の文字を16ビット(2バイト)に納めようなどというアホなことを画策したため基本的には破綻したらしい。また、コンパクトにしたいがため、中国語と日本語の漢字を一緒くたにするなど、一部の国の人にいらぬ因縁をつけてしまったみたいです。

現在では16ビットに収まった部分を BMP(基本多言語面)とか呼んで、それ以外は拡張領域(32ビット)に納めることでなんとかしているらしいです。

多くの漢字は 16ビットに収まっているので、日本語の文書は UTF-8 より無駄がないということになりますが、中途半端でビッグとかリトルなんとかの問題も起こりやすく、 Unicode に「複雑怪奇でダメダメ規格」と言う印象を持つ要因かと思うので、個人的には一回消えて出直して欲しい。

Windows の Unicode は UTF-16

UTF-32
文字の処理は単一長の方が良いため(?)存在する。多言語を同時に処理するようなプログラムでは有利なのだと思いますが、英数字なども全て32ビット使うため、ASCII文字のみのものを UTF-32 に変換すると 4倍になります。

まあ、いくらテキストデータの容量など知れたものと言っても標準規格にはなりにくいと思います。

BOM (Byte Order Mark)
Unicode の自動認識がらみのことですから、BOM の問題はテキストエディターの設定などで起きることです。

また、UTF-8 には関係ないようで関係あったりなど曖昧なところが多くあるようなので明示の必要があるだけで、変換プログラムやそれこそテキストエディターなどを作るのでなければ、プログラムコードの記述時に意識することではありません。

Shift-JIS
  • 多分、ほぼ日本語専用文字コード(ASCII文字を含む)
  • 日本語 MS-DOSに採用以来、日本語 Windows の標準
  • 日本語 Windows では ANSI が Shift-JIS らしい
  • 拡張コード(2バイト目)が ASCII コードを使ってる(制御コードを含む)、らしい

日本語 Windows のコマンドプロンプトはいまだに Shift-JIS のみ。

E-Mail : open@pen-jr.org

ご意見やご指摘は大歓迎ですが、質問など返信は期待しないでください

pen jr.
D言語研究
わかったつもりになるD言語
D言語友の会
News&Days
はじめてのブログ選び