『Cプログラミング診断室』目次次(第7章 文字処理は得意 配列サイズ)

第7章 文字処理は得意

改造


■改造方針■

まず考えるべきことは、変更の作業量と安全性です。すでに運用中なので、「安全」に変更して いかなければなりません。今回の変更は、思い切った変更というより、プログラムが突然混乱に陥っ たりすることのない、スムーズなプログラム改良を試みてみます。運用中のシステムの変更の困難 さは、変更そのものより、途中で問題を起こさずに、いかに移行するかにあります。

さて、個々のメッセージに名前がついていて、この名前はプログラムの多数の個所から参照され ています。ですから、この名前を変えてしまうことは相当危険を伴うので、メッセージと名前の対 応はそのまま活かすことにしてみます。もちろん、安全のためです。

■下駄を履かせるな■

表示位置データは、表示位置に一定の値(下駄、オフセット、バイアスなどと呼ばれる)を加え たものになっています。このように、ある値をあらかじめ加えておくことを、「下駄を履かせる」 とか言います。

表示位置データを使うとき、いつもこの値( W3XOFFSET,W3YOFFSET)を引かなければなりません。 ご覧いただいているリストでは、下駄を脱がせている個所は3つだけですが、プログラム全体では いっぱい出てきます。

データとして、初めから欲しい値が入っていれば、いちいち下駄を脱がせる手間はいりません。 こんな手間がかかるようにしていると、下駄を脱がせ忘れて「バグ」の原因になりやすいです。

データは、必ず「本当」のデータをそのまま入れるように心がけるべきです。本当のデータに何 らかの加工をしてしまうには、相当重大な理由がなければなりません。

■文字列サイズ

ところで、
    char  str[] = "\0\1ABC";
    printf( "%d\n", sizeof( str ) );
    printf( "%d\n", strlen( str ) );
はどうなるでしょう。「文字列としての長さ」は strlen(str)で、文字列の最初に'\0'が入ってい るので、0になります。しかし、sizeof(str)は6 になります。これは、文字列中に'\0'が含まれ ていても、文字列データとして確保する領域が終るわけではありません。この文字列では、\で始 まる文字コードが2文字、その後に普通の文字が3文字あり、最後に文字列の終了を示す'\0'が付加 されるので、全体で6バイトの文字列になります。もちろん、sizeof(str)とsizeof("\0\1ABC")は 同じ値になります。

重要なことなので、もういちど繰り返します。2重引用符(″)で囲まれた文字列リテラルの中に は、任意の文字コードが書けます。'\0'が途中にいくつ入ってもだいじょうぶです。文字列リテラ ルは、さらに最後に'\0'が付加されてメモリ上に確保されます。

文字列として扱うとき、たとえばstrで始まる文字列操作関数などでは、'\0'を文字列の終りと 判定するので、それより後ろのデータは存在しても無視されます。

というわけで、″ でくくった文字列中には、任意の文字コードを入れることができ、表示位置デー タをそのまま入れてだいじょうぶです。

メッセージデータの先頭2バイト分を修正したヘッダーファイルが、リスト7−4です。リスト 7−1では、M720とM730の先頭2バイトが抜けていましたが、正しい値がわからないので、適当な 値(0)をセットしておきました。

 
リスト7−4 ヘッダファイル(漢字メッセージの定義)【修正版】

     1	/********************************************************************************/
     2	/*                                                                              */
     3	/*         コマンドメッセージの定義                                             */
     4	/*                                                                              */
     5	/********************************************************************************/
     6	#ifndef _KANJI_H
     7	#define _KANJI_H
     8	
     9	/***  変数名,x,y,メッセージ   ***/
    10	
    11	typedef struct {
    12	        unsigned char   x, y;
    13	        char            text[1];
    14	} message;
    15	
    16	#define   M140 "\d034\d017■MOD"
    17	#define   M150 "\d003\d017■中止"
    18	#define   M160 "\d010\d017■実行"
    19	#define   M170 "\d017\d017■印字"
    20	#define   M180 "\d024\d017■次へ"
    21	#define   M190 "\d003\d017■中断"
    22	#define   M192 "\d031\d017■設定"
    23	#define   M194 "\d031\d017■全削除"
    24	
    25	#define   M200 "\d000\d000子局名選択"
    26	#define   M210 "\d000\d000制御項目選択"
    27	#define   M220 "\d000\d000受信中"
    28	#define   M222 "\d000\d000受信できませんでした。"
    29	#define   M224 "\d000\d000ステーション・エラーが生じました。"
    30	#define   M226 "\d000\d000ライン・エラーが生じました。"
    31	
    32	#define   M692 "\d005\d002運用開始"
    33	#define   M694 "\d005\d003運用終了"
    34	
    35	#define   M720 "\d000\d000運用時間"
    36	#define   M730 "\d000\d000運用時間設定"
    37	
    38	#define   M900 "\d025\d002●0"
    39	#define   M910 "\d025\d003●1"
    40	#define   M920 "\d025\d004●2"
    41	#define   M930 "\d025\d005●3"
    42	#define   M940 "\d025\d006●4"
    43	#define   M950 "\d025\d007●5"
    44	#define   M960 "\d025\d008●6"
    45	#define   M970 "\d025\d009●7"
    46	#define   M980 "\d025\d010●8"
    47	#define   M990 "\d025\d011●9"
    48	
    49	#endif _KANJI_H
    50	
    51	/********************************************************************************/
    52	/*                             End of File "kanji.h"                            */
    53	/********************************************************************************/

ところで、余談ですが、

    char  *str = "\0\1ABC";
となったときには、sizeof(str) と strlen(str) の値はどうなるでしょうか。str[] と *str は 同じはずだから、値は同じでしょうか。str[]は配列で、宣言の場所により、静的データエリアで あったり、スタック上であったりしますが、とにかくメモリ上に文字列リテラルのサイズ分の領域 が確保されます。しかし、*str はポインタなので、sizeof(str)は文字列リテラルのサイズとは無 関係にポインタのバイト数の2とか4になります。

■構造体■

メッセージデータは、表示位置と実際のメッセージデータの「組合わせ」なので、構造体を使う ことが自然です。文字列の先頭2バイトに変な細工をして入れている表示位置データとメッセージ テキストを上手に操作するために、構造体
    typedef struct {
        unsigned char    x, y;
        char             text[1];
    } message;
を用意します。

メッセージ表示関数は、DispMsg( M150 ); のように呼び出します。M150は、もちろんマクロ定 義されていて、実際は文字列です。

さて、リスト7−5の関数DispMsgを見てください。この引数は、message型へのポインタmesに なっています。mesには、当然、文字列M150のアドレスが入っています。しかし、関数DispMsgでは、 message型へのポインタとみなし、mex->xで先頭の 1バイトに入っているx座標値を、mex->yで次の 1バイトに入っているy座標値を参照しています。

メッセージの文字列は、mes->textで参照しています。message型の文字配列textのサイズは1で すが、mes->textで文字列の先頭アドレスを示しているだけです。この関係を図7−1に示します。

図7−1 文字列データをmessage型データとして解決する。

      定義  ←───→  参照

     char[]             message

           +----------+
         0 |    43    | x
           +-        -+
         1 |    17    | y
           +-        -+
         2 |   +--+   | text[0]
           +-  |  |  -+
         3 |   +--+   |      1
           +-        -+
         4 |          |      2
           +-   中   -+
         5 |          |      3
           +-        -+
         6 |          |      4
           +-   止   -+
         7 |          |      5
           +-        -+
         8 |     0    |      6
           +----------+

 
リスト7−5 漢字メッセージの表示関数【修正版】

     1	/********************************************************************************/
     2	/*                                                                              */
     3	/*       コマンド表示                                                           */
     4	/*                                                                              */
     5	/********************************************************************************/
     6	
     7	#include "mydefs.h"
     8	
     9	/*******************************************************************************/
    10	/*      ライトペン入力のためのメッセージ表示                                   */
    11	/*           DispMsg       1メッセージのみの表示                              */
    12	/*           DispMsgs      複数メッセージ表示                                  */
    13	/*******************************************************************************/
    14	
    15	DispMsg( mes )
    16	  message       *mes;
    17	{
    18	        locate( stderr, mes->x, mes->y );
    19	        fprintf( wpath, "%s", mes->text );
    20	        fflush( wpath );
    21	}
    22	
    23	DispMsgs( n, messages )
    24	  int           n;
    25	  message       **messages;
    26	{
    27	        while( n-- > 0 ) {
    28	                DispMsg( *messages++ );
    29	        }
    30	}
    31	
    32	/********************************************************************************/
    33	/*      標準データ表示(受信中、中止、印字、次へ)                              */
    34	/********************************************************************************/
    35	StandDsp(timeup)
    36	  int     timeup;                       /* time until time up           */
    37	{
    38	        static char *messages[] = { M150, M170, M180 };
    39	        int     d3_cnt;                 /* d3_ data count               */
    40	        int     d3_offset;              /* d3_ structure offset         */
    41	        int     getd3data;              /* get d3 data count            */
    42	
    43	        cls2();
    44	        DispMsg( M220 );
    45	
    46	        /* 応答データの取得 */
    47	        switch( Receive(timeup) ) {
    48	        case TIMEUP:
    49	                cls2();
    50	                DispMsg( M222 );
    51	                tsleep(3 * TPS);
    52	                return(RETURN);
    53	        case STD_ERROR:
    54	                switch(TdRpt.tr_lineno.str2[1]){
    55	                case CE_STATERR:
    56	                        cls2();
    57	                        DispMsg( M224 );
    58	                        break;
    59	                case CE_LINEERR:
    60	                        cls2();
    61	                        DispMsg( M226 );
    62	                        break;
    63	                default:
    64	                        break;
    65	                }
    66	                tsleep(3 * TPS);
    67	                return(RETURN);
    68	        }
    69	
    70	        /* 表示 */
    71	        d3_cnt = d3expand(d3_count);
    72	        d3_offset = 0;
    73	
    74	        while( d3_cnt > 0 ) {
    75	                cls2();
    76	                locate( stderr, DSPLINEX, DSPLINEY );
    77	                getd3data = d3disp(wpath, d3_offset, d3_cnt, OFF);
    78	
    79	                DispMsgs( Number(messages), messages );
    80	
    81	                switch( ReadLPen( Number(messages), messages, 60 * 1) ) {
    82	                case 1: /* 中止   */
    83	                        return(RETURN);
    84	                case 2: /* 印字   */
    85	                        if(PaperChk() == ON){
    86	                                PaperChkPutc(CR);
    87	                                d3disp(ppath, d3_offset, d3_cnt, ON);
    88	                                PaperChkPutc(CR);
    89	                        }
    90	                        break;
    91	                case 3: /* 次へ   */
    92	                case TIMEUP:
    93	                        break;
    94	                }
    95	                d3_offset += getd3data;
    96	                d3_cnt -= getd3data;
    97	        }
    98	
    99	        return(CONTINUE);
   100	}
   101	
   102	/********************************************************************************/
   103	/*              プリンタチェック後に1文字印字                            */
   104	/********************************************************************************/
   105	PaperChkPutc( c )
   106	  char  c;
   107	{
   108	        if( PaperChk() )
   109	                putc( c, ppath );
   110	}
   111	
   112	/********************************************************************************/
   113	/*                      End of File "cmdmake.c"                                 */
   114	/********************************************************************************/

文字配列textのサイズは不用なので、

	char	text[];
と指定したいところですが、これでは構造体のサイズが不定になり、コンパイルエラーがでます。 それを避けるために、[1]としているだけで、[2] でも、[100]でも、実際には何でもかまいません。

元の文字列データの変更は面倒なので、下駄だけとり除きました。こうすると、可能な限り少な い変更で、カーソル位置とメッセージ文字列が構造体のメンバーとして取り扱えるようになります。

本来は違うものを引数で渡し、関数の側で、都合の良い型とみなして処理すると、非常に便利に なることがあります。今回のは、ちょっと「せこい」とは思いますが、すっきりします。

■初期化■

元のプログラムでは、複数のメッセージデータを配列に代入しています。リスト7−3の129〜 140行の代入の並びは、ちょっと異常です。毎回、同一配列にメッセージデータを代入して、メッ セージデータ配列としていますが、それぞれに1つの配列を用意し、初期化すれば、関数の途中に だらだらと代入文を羅列しなくて済みます。

メッセージデータ配列は、実行の途中では変更されないので、関数内部にstatic配列として確保 し、初期値式として ={}の中にメッセージデータのアドレスを並べます。もともとメッセージデー タはマクロ定義された文字列なので、マクロ名を並べるだけです。これで冗長なコーディングを短 く、すっきりできます。

それに、メッセージデータの配列は固定したものですから、関数の途中で代入するのは不自然で す。初期化によってデータを設定し、実行中に変化しないことを示す方が良いでしょう。

ここで作った配列を使ってメッセージ表示を行なう関数がDispMsgsです。この関数は、

    DispMsgs( n, messages )
      int	n;
      message	**messages;
なので、メッセージデータへのポインタの配列が渡されたものとみなします。この関数は、元の DispMsgと同じ機能です。

もっとエレガントなDispMsgsの仕様は、メッセージ個数の n をなくしてしまうことです。その ためには、メッセージの最後を示すための NULLポインタを最後のメッセージの次に入れておく必 要があります。つまり、

    static char  *mes_coms[] = { M150, M170, M180, NULL };
のようにするのです。このためのDispMsgsの変更は読者への課題としましょう。

■もがき■

メッセージデータを、もっと大幅に変更してしまうのであれば、その他にもいろいろな書き方が 考えられます。メッセージもマクロではなく、始めから構造体にし、変数に入れてしまうことも考 えられます。

また、ANSIのCでは文字列リテラルを並べる(間に , を入れない)と、全体で1つの文字列リ テラルになるので、それを活かすと、位置データとメッセージ自体を分離できます。つまり、

   #define  M150  "\d003\d017■中止"
   #define  M150  "\d003" "\d017" "■中止"
と書けます。さらにマクロを使えば、
   #define POS( x,y )   #x #y
   #define M150  POS(\d003,\d017) "■中止"
とも書けます。本当は、POS(3,17)と書きたいのですが、どうもそれは無理のようです。

でも、文字列として細工をするのは、この辺りが限界でしょうか。理想は、始めから構造体デー タにしてしまうことでしょう。


Copyright1996 Hirofumi Fujiwara. No reproduction or republication without written permission
『Cプログラミング診断室』目次次(第7章 文字処理は得意 配列サイズ)