プリント基板設計・シミュレーション

TOP > アポロレポート > コラム > 第22回 プログラミングについて 『日本語の話』
コラム
2022/11/29

第22回 プログラミングについて 『日本語の話』

アポロレポート

『日本語の話』

 

  前回は大きくはまってしまった近況をお話ししました。これからしばらくの間はテキストエディタの開発中に困ってしまったことなどからピックアップしてお話しします。

 さて今回は日本語の話をしましょう。私がコンピュータと付き合うことになった当初はDECのVAXという計算機で、その頃のOSや端末はまったく日本語はサポートされていませんでした。当時はプログラムを書くにしても1文字は必ず1バイトだったので文字列の操作はかなり楽だったように記憶しています。この計算機上でもテキストエディタ(ラインエディタとスクリーンエディタとの中間のような変なものでしたが)を作ったことがありましたが、カーソルを1文字移動させるには1バイト分移動させれば十分でした。また端末は文字端末(VT100)でしたので文字列のみの操作で実現できたのです。

 やがてこの計算機のOSや端末にも日本語が使用できるようになりました。DECにはDEC独自のDEC漢字という文字セットがあって日本語文字の文字コードは全て2バイト。そして2バイトとも最上位ビットがオンという規則があり、全角文字しかありませんでしたので2バイト文字か否かを判断するにはその最上位ビットを調べるだけで事足りました。また端末も文字端末でしたので2バイト文字の幅は1バイト文字の2倍と単純計算が成り立ちました。

 この事情が変わったのが、Xウインドウ上でアプリケーションを書き始めたときからです。Xウインドウ(Windowsも同じですが..)では誠に様々な種類のフォントを任意に使用することができ、また文字のプロポーションが固定のものと可変のものとがあるのです。ということは2バイト文字の幅は1バイト文字の2倍といった単純計算は成り立たず、文字列の幅を算出するXウインドウの関数を呼び出して処理しなければならなくなってしまいました。

 UNIXを使うようになって混乱したのは、半角のアルファベットや記号文字についてはASCIIコードですので問題はありませんが、こと日本語文字となるとそう単純にはいかないのです。現在HP、DEC、DGなどのUNIXマシンを使っていますが現在のUNIXで扱える日本語文字コードはEUC、JIS、SJISなどがあって、ftpなどでファイルをやり取りしていると日本語のテキストファイルがメチャメチャになってしまうことがあるのです。ftpによってはテキストモードで転送すると自動的に文字コードの変換を行ってくれるものもあるのですが、そうでないもののときには文字コードの変換が必要になってしまうのです。

 もともと日本語のコード体系はJISコードという日本の規格がありました。このコードはシフトイン、シフトアウトといった特殊なエスケープコードを使用して、半角文字や全角文字などの区別をするようになっています。またこの方式では最上位ビットは使用しないようになっていますので、そのビットはパリティビットとして使用することが可能となっているのです。ただ難点は任意のバイトのみを調べてもそれがいったい半角文字なのか全角文字なのかはさっぱり分からず、またファイルサイズがシフト命令を挿入するので大きくなってしまうことです。その上、シフトコードにも色々と種類があるようで混乱の元になっています。
 SJIS(シフトJIS)コードは、確か日本でパソコンが使われるようになったときにJISコードは扱いにくいということでマイクロソフトが作ったものと記憶しています。SJISコードはJISコードではシフト命令でシフトしていた部分を最初からシフト(最上位ビットを使用することになる)させておくもので、特殊なシフトコードは使用しなくてもいい文字セットになっています。これにもやっぱり難点があって、後で説明しますが任意の位置のバイトを調べても正確に文字の種類を特定できないものがあるのです。
 UNIXにはUNIXでEUCという日本語文字コードがあります。これは日本語文字は全て2バイトなのですが、2バイトコードで半角文字(カタカナなど)も表現しているので注意が必要です。
 最近ではインターネットが流行していてこの文字コードの違いなどで文字化けの問題が生じるので、それ専用の文字セットを規定使用とする動きもあるようです。

 ハードもソフトもそれまでの失敗や不具合を基礎にして改善され、進化して行くものです。文字コードのこの例外ではありませんので、いろいろな文字コードができてしまったのはこれも仕方がないことだと思いますが、プログラムを書く人間の立場から言えばちょっときつい感じがあります。

 現在作っているテキストエディタはWindows専用のものですから、基本的にはSJISコードを操作します。この2バイトの文字コードは、

 第1バイト 81H~9FH、E0H~FCH
 第2バイト 40H~7EH、80H~ECH

となっています。第1バイトの最上位ビットは常にオンですが、第2バイトはオンのものとオフのものがあります。特に40H~7EHの範囲は通常のアスキー文字の範囲そのものです。また、80H~ECHの範囲は第1バイトの範囲と重なっています。ということは半角文字と全角文字が混在している文字列の任意の位置の文字種類はその値だけを調べても判断がつかないことになります。

 いま、半角と全角の文字が混在している文字列を1文字づつ表示するプログラムを見てみましょう。

 display_char_from_top(string)
 char *string;
 {
  unsigned char *s;

  s=(unsigned char *)string;

  while(*s){
   if(*s>=0x81 && *s<=0x9f
   || *s>=0xe0 && *s<=0xfc){   //2バイト文字。
    printf("%c%c\n",*s,*(s+1));
    s+=2;
   }
   else{             //1バイト文字。
    printf("%c\n",*s);
    s+=1;
   }
  }
 }

 文字列の先頭から調べていった場合には、2バイト文字の先頭は一意に決定できますので正常に動作します。

 次に上記の関数を文字列の末尾から表示するものを作ってみたときに、2バイト文字の第2バイトのみを調べる方式では、

 display_char_from_tail(string)
 char *string;
 {
  unsigned char *s;

  s=(unsigned char *)string+strlen(string)-1;

  while(s>=string){
   if(s-string==1){          //1バイト文字。
    printf("%c\n",*s);
    s-=1;
   }
   else if(*s>=0x40 && *s<=0x7e
      || *s>=0x80 && *s<=0xec){   //2バイト文字。
    printf("%c%c\n",*(s-1),*s);
    s-=2;
   }
   else{                //1バイト文字。
    printf("%c\n",*s);
    s-=1;
   }
  }
 }

となります。この関数を使って次のように文字を表現してみると、

 main()
 {
  display_char_from_tail("abcABCABCabcABC");
 }

 BC
 cA
 ab
 C
 B
 A
 BC
 cA
 b

となってしまい予想していた結果とは全然違ったものになってしまいます。そこで、2バイト文字は第1バイトを検査するように直してみましょう。

 display_char_from_tail(string)
 char *string;
 {
  unsigned char *s;

  s=(unsigned char *)string+strlen(string)-1;

  while(s>=string){
   if(s-string==1){            //1バイト文字。
    printf("%c\n",*s);
    s-=1;
   }
   else if(*(s-1)>=0x81 && *(s-1)<=0x9f
      || *(s-1)>=0xe0 && *(s-1)<=0xfc){ //2バイト文字。
    printf("%c%c\n",*(s-1),*s);
    s-=2;
   }
   else{                  //1バイト文字。
    printf("%c\n",*s);
    s-=1;
   }
  }
 }

同じく実行してみると、

 C
 B
 A
 c
 b
 a
 C
 B
 A
 C
 B
 A
 c
 b
 a

となって正しく実行できました。それでは "菩提樹define" という文字列ではどうなるでしょうか、

 e
 n
 i
 f
 e
  "
  "
 附

となってしまいます。これはいったいどういうことなのでしょうか。問題は2バイト文字の第2バイトの範囲に問題があるのです。
文字列 "菩提樹define" の "樹d"の部分がいったいどうなっているのかを16進数で表してみると、

 8E F7 64
 樹  d

となっています。いま d のところをポインタが示しているとし、その1つ前の文字の値を検査してみると、確かにその値は第1バイトの範囲 E0H~FCH にあるのです。ということは F7 64 を2バイト文字と判断してしまってでたらめな動作になってしまったのです。

 SJISコードの最大の欠点が第1バイトと第2バイトの範囲に重なりがあることなのです。この重なりがあることで、SJISコードでは安直にその前後の値の判断で文字の種類が決定できないのです。

 結局上記の例のように文字列の末尾から1文字づつ表示するには、文字列の先頭から調べていかなければならないということになるのです。次に再帰呼び出しを使った例を紹介します。

  display_char_from_tail(string)
  char *string;
  {
    if(*string){
      unsigned char *s;

      s=string;

      if(*s>=0x81 && *s<=0x9f
      || *s>=0xe0 && *s<=0xfc){
        display_char_from_tail(s+2);
        printf("%c%c\n",*s,*(s+1));
      }
      else{
        display_char_from_tail(s+1);
        printf("%c\n",*s);
      }
    }
  }

再帰呼び出しなので、文字列が長くなるとスタックが深くなってしまいますので、実際には一度文字列の先頭から文字の先頭位置を調べて配列などに記憶しておき、逆方向に表示する方法をとるのがいいかと思います。

 それではまた次回。


そのお悩み、
アポロ技研に話してみませんか?

アポロ技研でワンランク上の物創りへ!
そのお悩み、アポロ技研に話してみませんか?