先日
チラリと触れた、Windows XPのメモ帳で「Bush hid the facts」という(ASCII形式またはその互換形式で保存された)テキストファイルを開くと文字化けしてしまう(Unicode(UTF-16LE)として読み込む)、という現象についてだが、これがインターネット界隈で話題になっていたのはもう3年ほど昔の話であり、おそらくその謎(?)を解説しているWebページも探せばいろいろあるのだろうが、面白いので自分なりにちょっと調べてみた。
この現象を僕が知ったのは、このブログにもときどきコメントを残してくれる
木公さんのブログで紹介されていたから(3年前)。彼は「Bush hid the facts」「Takenaka hid the facts」「Koizumi hid the facts」の3つを試してみて、「Koizumi」以外の2つが文字化けすることを報告している(文字化けメモ帳の画像あり)。「 hid the facts」の部分は同一なわけでいったい何がこの現象を引き起こすキーなのか。
まず、「Bush hid the facts」を通常保存(日本語WindowsではシフトJIS形式(この内容ならASCII形式と同一))した場合にできるテキストファイル(18バイト)の内容をバイナリエディタで見てみた。16進数で表記すると
42 75 73 68 20 68 69 64
20 74 68 65 20 66 61 63
74 73
となっていて、半角アルファベットと半角スペースに割り当てられたASCIIコードが順番に並んでいるだけの正常なファイルであることがわかる(ちなみに、ASCIIコードは1文字7ビットだが、こういう場合は先頭に0を付加した8ビット(=1バイト)で1文字を表わすのだろう)。
このファイルをUTF-16LEとして読み込むと、コンピュータとしては次のような2バイトの文字コードが9個並んでいると把握することになる(UTF-16には4バイトコードも存在するが、それらはDで始まるのでここに並んでいるのは2バイトコードだと判定できる)。
7542 6873 6820 6469
7420 6568 6620 6361
7374
(それぞれの文字コードの数値(16進数)が上位バイトと下位バイトで引っくり返っている(例えば、冒頭の「42 75」が「7542」となっている)のは、「リトルエンディアン(LE)」形式だから。インテル系のCPUは16ビット情報をレジスタに読み込む際に、メモリの下位8ビットをレジスタの下位8ビット、メモリの上位8ビットをレジスタの上位8ビットにロードするのだが(そういう意味では極めて自然なのだが)、ビットに関しては左が上位、右が下位、メモリアドレスやファイルの「バイトの流れ」に関しては右が上位、左が下位として通常表現されるので(メモリに関しては水平方向ではなく上下方向で表現されることも多いが)、上位バイトと下位バイトが入れ替わっているように見えるのである。こういうことは8086アセンブラの入門書を読むと書いてある。)
上述の木公さんのブログに掲載されている画像を見ると、「■■■摩■■映■■」みたいな感じに文字化けしている。「摩」と「映」が表示されているのは、それぞれのUnicodeが6469と6620だからだ。
それでは、他の数値がUnicodeとして不正な値なのかというと、それは違う。6361〜7542の範囲に収まっており、これは4E00〜9FFF(9FA0?)という「CJK統合漢字」と呼ばれる広大な領域に収まっており、どれもChina、Japan、Koreaで用いられている漢字に割り当てられている数値なのだ(MSゴシックのような日本語フォントでは「■」みたいな感じに表示されてしまうが、Unicodeフォント(例えば、Arial Unicode MS)や中国語フォント(例えば、SimHei)を指定すれば「畂桳栠摩琠敨映捡獴」のように全て漢字として表示される)。つまり、(例えば)中国語でテキストファイルを作ってUTF-16LE形式で保存すれば、おそらくこんな感じのファイルができるのである。
(例えば、『論語』の有名なフレーズ「有朋自遠方来不亦楽乎」をUTF-16LEで保存してみたところ、
6709 670B 81EA 9060
65B9 6765 4E0D 4EA6
697D 4E4E
となった。)
「Takenaka hid the facts」も同様で、ASCII形式だと
54 61 6B 65 6E 61 6B 61
20 68 69 64 20 74 68 65
20 66 61 63 74 73
Unicodeとして読むと
6154 656B 616E 616B
6820 6469 7420 6568
6620 6361 7374
となり、やはりUnicode文字列として不自然な点はない(のだと思う)。
ところが、「Koizumi hid the facts」にはいろいろ問題がある。まず、ASCII形式だと
4B 6F 69 7A 75 6D 69 20
68 69 64 20 74 68 65 20
66 61 63 74 73
で、これまでと大差ないように思われるが、Unicodeとして読むと
6F4B 7A69 6D75 2069
6968 2064 6874 2065
6166 7463 ..73
2069、2064、2065が「CJK統合漢字」ではなく「一般句読点」と呼ばれる範囲の数値であること(しかも、どうも2065と2069は領域は確保されているものの、実際には文字が割り当てられていないっぽい(嘘?))、そして何よりも、最後に「73」という「字余り」が発生することである! 「Koizumi hid the facts」の文字数は21だから、ASCII形式でのテキストファイルのサイズは21バイト。UTF-16は(記号を含む)全ての文字に2バイトか4バイトのコードを割り当てているから、UTF-16形式で保存されているならば、ファイルサイズは必ず偶数になる(のだと思う)。ファイルの中身を見なくても、ファイルサイズを見ただけでUTF-16ではないと判定できるのだ。
こうやっていろいろ考えてみると、「Bush hid the facts」はUTF-16形式だと誤認させるように意図的に作られた巧妙な例なのではないかと感じられてくる。空白スペースの位置が絶妙なこと(偶数位置に現れたらアウト)、最後にピリオド(ASCIIコードは「2E」)がないこと(この例では奇数位置でセーフだが、字余りになる)、改行していないこと(CRにもLFにも、ASCIIでは1バイト(0Dと0A)、UTF-16では2バイト(000Dと000A)を当てているので、保存形式がすぐわかる)。
ちなみに、CR+LFをUTF-16LEとして読み込んだ「0A0D」は「グルムキー」と呼ばれる文字に当てられているようだ(僕のコンピュータでは「ຏ」として表示されてしまい、文字としては表示されない。※Microsoft Internet ExplorerとMozilla FirefoxとGoogle Chromeでそれぞれ違って表示される)。上位・下位を更に引っくり返した「0D0A」は「マラヤラム」と呼ばれる文字で、何と「
ഊ」。世の中にはいろんな文字があるんだな、と感心させられる。
と言うわけで、今日の文字コード探検はこれにて終了。

0