人間は言葉が同じなためCP932を始めた。人々の言語を乱し通じない違う文字列を話させるようにしよう

投稿日:2019年04月14日 18時18分02秒

多分今日は概論で終わっちゃうしもっと偉い人が色々言ってるとは思いますが改めて文字コードについてまとめます。
コンピュータが配線を卒業しコードを導入して以来文字コードは悩みのネタであり、その変換はプログラマの飯の種でもありました。ASCIIコードは今や世界を支配していますが、その前はEBCDIC (Extended Binary Coded Decimal Interchange Code) というコードもありました。昔のDEC等の10進コンピュータの名残なんでしょうか。中にBCDという10進を表す文字が入っています。こちらの文字コードも死に絶えたりはせずに一子相伝の発展をしてるようですが、まずは話を端折ります。

コンピュータは大抵の国でASCIIコードを扱っておりましたが、どうしてもマルチバイトが欲しかったのが日本です。漢字の国ですからね。初期のコンピュータではasciiコードで使わない領域にカタカナやひらがなを表示しておりました。日本ではどうしても英文ワープロに相当するものが欲しかったのでしょう、最初に普及したのがワープロ専用機です。初代東芝のワープロ専用機JW-10では独自コード(CO.-59)が使われていたそうです。wikiで見ると「北海道新聞・中部日本新聞・西日本新聞・産経新聞・中国新聞・共同通信の六社間での共通に記事データを交換するための文字コードである。このコード表は漢テレファックス符号及び文字配列表と呼ばれる」とあります。既に存在しない職業ですがテレファックスオペレータ業務を念頭においた製品だったのかもしれません。

yasuokaの日記: JW-10の文字コード
[browser-shot url=”https://slashdot.jp/journal/491791/JW-10%E3%81%AE%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89″ width=”400″]

zu02文字コードの発展経緯から役割と仕組みを学ぶ

ここで文字コードを標準化しようというので出てきたのがJISです。日本でJISコードといえばISO-2022-JPです。メールのソフトを書いてる人は見ることもあるんじゃないでしょうか。これをベースにPCへの実装を行ったのが(会社の方の)ASCIIやマイクロソフトです。出来上がったのがShift-JIS、今でも多く使われていますし、ちょっと方言ではありますがCP932というのがマイクロソフト内部では生きてます。特徴としてはasciiが1バイト、漢字が2バイト、大きさもasciiが1に対し漢字が2という計算しやすい方式であったことですね。今ではよく意味が通じないですが半角漢字というサイズが1の、ひらがななんてのもありました。
このISO-2022-JP(JIS X 0208)を独自にUnixにマッピングしたのがEUC-JPです。漢字は2~3バイトで表され、0x80 – 0xFFの領域を使うのでasciiと完全に住み分けたのが特徴です。サーバといえばApacheであったことからEUC-JPも結構なシェアがあったのでした。
ここでJISx0208をベースとしてSJIS,EUC,JISの相互変換、さらにはEBCDICへの変換と変換するだけで仕事になる時代が発生します。これはインターネットの時代になりさらに拡大していきます。メールがISO-2022-JPである理由の一つはリレーするコンピュータにはEBCDICな物も含まれたからです。今もそうですがメールは多くのコンピュータを経由して送付されてました。ISO-2022-JPには漢字イン、漢字アウトの概念があり7ビットで表現されているので内容は通常の文字コードとして通用します。だからASCIIEBCDICが通る環境ならISO-2022-JPも通るように設計されていたのです。

時代は流れてインターネットの世界にはなりましたが今でも文字化けはなくなりません。文字コードは100%自動判別できませんし、前述のCP932やEUC,ISO-2022-JPをまとめあげようとUNICODEが出てきたからです。

これらの文字コードとは文字表現と数値とのマッピングであり実際に使うためにはコンピュータ言語で表現しなくてはなりません。そこで更に面倒な問題が勃発します。
CP932(SJIS)の世界ではコンピュータ言語は概ねCやC++であり文字とはchar、文字列とはchar*で表現されていました。1文字が8ビットで表現できるという前提です。SJISやEUCの8ビット目を利用する文字コードはcharで表現された場合は負数になり整数に代入したりするだけで値が変更されてしまい面倒でした。そこで漢字を含む可能性がある場合には文字はunsigned char,文字列はunsigned char*これらをBYTEと呼ぼうと解決されたのでした。

これらの表現はUTF-8やUTF-16に一元化されていこうとします。UTFとはUCS-2やUCS-4(Unicode)という基準で定義される文字集合を用いて記述された文字列をコンピュータが扱いやすいようにバイト列に変換する方式です。1文字を1~6バイト(現状では最長4バイト)の可変長の数値に変換するUTF-8、UCS-2の集合の中にUCS-4の一部の文字を埋め込むためのUTF-16、Unicodeをメールで使用するためのUTF-7、すべてのUCS-4文字を4バイトで表現するUTF-32の4種類があります。
志は高いものの、ASCII,ECU-JP、SJIS文化圏にUnicodeが殴りこむためには、俺の文書はUnicode使ってるからね!よろしく解釈してくださいよ!と下手にでる必要がありました。しかもそれは文書読み込みの先頭すなわちプログラムが解釈する頭で判断すると処理が楽でした。そこで文書の先頭にバイトオーダーマーク(BOM)と呼ばれるマークを記入することになってしまったのです。識別子なので、UTF7,UTF8,UTF16,UTF32全てに別のBOMが付きます。または面倒な場合にはつかない場合もあります。バイナリデータのアプリケーションの場合勝手に先頭にマークをつけると処理がおかしな事になってしまいます。。
その結果、なんということでしょう。BOMあり、BOMなし、UTFの種類、CPUの種別によってUTF8,UTF8N,UTF-16,UTF-16BE,UTF-16LE,UTF-32,UTF-32BE,UTF-32BE,UTF-7と煩雑な区別をする必要が出てきたのです。
このような文字の乱立をコンパイラ言語ではもはやBYTEだけでは吸収できませんでした。文字の混在はまずソースを読み込むところから発生しました。
[table caption=”コンパイラと文字コード” width=”500″ colwidth=”50|20|20|20|80″ colalign=”left|center|center|center|left”]
“文字コード”,”gcc”,”VC++”,”VS”,”備考”
“UTF-8/BOM無し”,”◯”,”☓”,”○”,”VC++でコンパイルエラー”
“UTF-8/BOM付き”,”☓”,”○”,”○”,”gccで先頭に変な文字(BOM)があると認識されコンパイルエラー”
“EUC-JP”,” △”,”☓”,”☓”,gccではコンパイルオプションを指定することで可能”
“Shift-JIS”,”△”,”○”,”○”,”gccではコンパイルオプションを指定することで可能”
[/table]

VC++とgccの両方でコンパイル
[browser-shot url=”https://blog.taishiwoidake.com/?p=380″ width=”400″]
文字コードの違いによりコンパイラにかけるだけでも一苦労になりました。一応Shift-JISかUTF-8BOM無しが無難なようにも思われますが’\’記号を含むSJIS等のコードは動的に処理を誤らせる可能性がついて回ります。よってなるべくutfを使う方がよいかと思われます。さてこれらを言語内ではどのように表現するでしょうか。今回は歴史的経緯があるCで考えます。

1.char*
一番簡単に考えられるのはchar*またはunsigned char*として扱うという方法です。文字数が不明だったりソース内で可読でない可能性もあり各種ライブラリ関数も使えない場合もあるでしょうが、c言語の0がEOFで終端であるという考えに従えば実装は可能という強みがあります。ソース中でどうしても一部分だけ他言語の文字を入れたり、携帯向けの絵文字を扱ったりする場合には使わざるを得ないテクニックでもありました。問題点としては部分文字列や文字の切り出しといった関数が概ね無効である点でしょう。1文字が1バイトとは限らないからです。このように1文字が2バイト以上で表されるときマルチバイト文字列とよびます。これの亜流としてAnsi_Stringというものがあり「長さ+文字列」で管理する方法もあります。

2.WCHAR
これらを解決する1手法がワイド文字列です。マルチバイト処理系で文字数とバイト数が合致しない問題点を解決するために複数バイトを1文字の領域としたものがワイド文字列です。例えば2バイトが1文字の場合’A’はたかだか6bit程度しか消費しませんが、16ビットの入れ物に入れて表現します。16bitをchar16_t、32bitをchar32_tと表現します。またこれに入力する文字列側として先頭に’L’をつけてchar16_t *str = L”UTF16″;またはL’あ’のように表現します。汎用的にはwchat_tと表現します。特に16bit,32bitを意識する場合にはchar16_t* str=u”あいうえお”;また、char32_t* str=U”あいうえお”;のようにuUの大文字小文字で区別します。printfなどは引数では判断できない場合もあるようで、ワイド文字列についてはwprintfを使います。
ところで16ビットで表現できるのは全世界の文字ではなく一部です。ワイド文字列が対象とするのは現在アプリケーションが使われる場所に依存します。

3.TCHAR
マルチバイト文字列とワイド文字列を使う場合、処理系がunicodeをサポートしてる場合、してない場合で処理が変わってきてしまいます。そこでunicodeサポートならワイド文字列、未サポートならマルチバイト文字列と切り替えてくれる汎用文字列がtcharです。処理系によって違いますがUNICODEが#DEFINEされていると無条件にワイドキャラクターになりますので気をつけましょう。

以上の文字列で、実際にはどのように対応すればいいでしょうか。詳細は下記にありますが、tcharを使ってもマルチバイトからはかなり隔たりがあり修正箇所が多くなります。今後入力される文字列がutf-8になっていく事を考えるとマルチバイトで頑張り続ける方が可能性があるかもしれません。とりあえず今日はここくらいまでで。
[table caption=”コンパイラと文字コード” width=”600″]
“”,”TCHAR”,”マルチバイト決め打ち”,”ワイド文字決め打ち”
“宣言”,”#include ~~ :~~setlocale(LC_ALL, “Japanese_Japan.932″);”,”不要”,”#include ~~ :~~setlocale(LC_ALL, “Japanese_Japan.932″);”
“char*の表現”,”#include ~~ :~~_TCHAR* text = _T(“こんにちは”);~~_TCHAR ch = _T(\’窓\’);”,”char* text = “こんにちは”;”,”wchar_t* text = L”こんにちは”;”
“stringの表現”,”#include ~~typedef std::basic_string<_TCHAR> tstring;~~ :~~tstring message = _T(“こんにちは”);”,”#include ~~ :~~std::string message = “こんにちは”;”,”#include ~~ :~~std::wstring message = L”こんにちは”
“printf”,”_printf(text);”,”printf(text);”,”wprintf(text);”
“cout”,”#if defined(_UNICODE) || defined(UNICODE)~~# define tcout std::wcout~~#else~~# define tcout std::cout~~#endif~~#include ~~ :~~tcout << _T(“こんにちは”) << std::endl;”,”#include ~~ :~~std::cout << “こんにちは” << std::endl;”,”#include ~~ :\~~std::wcout << L”こんにちは” << std::endl;”
;”
[/table]

https://www.02.246.ne.jp/~torutk/cxx/vc/misc_tchar.html

[<< アマゾンから1万円のTVセットトップボックスFire TV!]

[ファイルの更新完了を検知する >>]