第43回
仕様設計からコーディングまで~タブ/スペース変換プログラムを作る(3)

mainから呼び出される各関数

先に紹介したmain関数の中から呼び出される関数のソースと、その動作を説明します。なお、1行を読み込んでタブをスペースに変換する、このプログラムの中枢と言えるtb2sp関数については、これまでに紹介してきたものとまったく同じなので、説明は省略します。

setdata~構造体にメンバをセットする(リスト6)

_CONV型構造体のメンバ"tab"(タブストップ幅)の値が最小値(MIN_TAB)~最大値(MAX_TAB)の間に収まっているかどうかを調べ、収まっていない場合は規定値(DEFAULT_TAB)を代入します。
if (p->tab < MIN_TAB)
  p->tab = DEFAULT_TAB;
else if (p->tab > MAX_TAB)
  p->tab = MAX_TAB;

その後、同じくCONV型構造体のメンバ"fname"(入力ファイル名)をもとにファイルをオープンします。ファイルがオープンできればTRUE、オープンできなければエラーメッセージを表示してFALSEを返します。
if ((p->fp = fopen(p->fname, "r")) == NULL) {
  fprintf(stderr, "%s がオープンできません.¥n", p->fname);
  return (FALSE);
}
return (TRUE);

リスト6:mainから呼び出されるsetdata関数
BOOL  setdata(_CONV *p)
{
  if (p->tab < MIN_TAB)
    p->tab = DEFAULT_TAB;
  else if (p->tab > MAX_TAB)
    p->tab = MAX_TAB;

  if ((p->fp = fopen(p->fname, "r")) == NULL) {
    fprintf(stderr, "%s がオープンできません.¥n", p->fname);
    return (FALSE);
  }
  return (TRUE);
}

makelink~コマンドラインを解析して構造体のリンクを生成する(リスト7)

引数にコマンドライン文字列(*argv[])を採り、それを解析して構造体のリンクを生成します。リンクが生成できればその先頭のポインタを返し、失敗すればNULLを返します。

まず、ローカル変数を宣言します。
int     i, tabsize;
_CONV   *p, *tmp, *start;

int型のiは、whileループによる繰り返しでリンクを生成する際のカウンタとして用います。ループの最初だけ、戻り値となる先頭のポインタを保存しなければならないので、1回目を数えるためだけに使用します。

int型のtabsizeは、コマンドラインで指定されたタブストップ幅を構造体のメンバtabに代入するために用います。

_CONV型のポインタは3つ用います。
*p    :作業用。whileループ内で現在処理中の_CONV構造体を示します。
*tmp  :作業用。構造体のメンバnextに次の構造体を代入する際の一次保存用として
        用います。
*start:先述した、リンクの先頭ポインタを保存します。

まず、コマンドラインから取得したタブストップ幅を、変数tabsizeに保存します。その際、代入後にパラーメータ文字列の配列を示す*argvをインクリメントし、次のパラメータ文字列を示すようにしておきます。
tabsize = atoi(*argv++);

次に、カウンタiを0で初期化し、whileループに入ります
i = 0;    /* カウンタを初期化 */

パラメータ文字列の配列*argvの終端はNULLとなっているので、whileループではこれをインクリメントしながらNULLになるまで処理を繰り返します。
while((*argv != NULL)) {
  if (i == 0) {  ← 1回目だけ実行する
    if ((p = malloc(sizeof(_CONV))) == NULL) {
      return(NULL);  ← メモリが確保できなければNULLを返す
    } else {
      start = p; ← 成功すればポインタをstartに保存
    }
  }
  p->tab = tabsize; ← タブストップ幅をメンバに代入
  strcpy(p->fname, *argv);  ← ファイル名をメンバにコピー
  tmp = p;  ← 現在のポインタを一次保存
  if ((p = malloc(sizeof(_CONV))) == NULL) {
        ↑次の構造体のメモリを確保
    return(NULL);
  } else {
    tmp->next = p;
        ↑ポインタをつなぐ
  }
  i++;  ← カウンタを進める
  argv++;  ← パラメータの配列を進める
}

ここまでで、一応ポインタのリンクが出来上がっています。しかし、ループの一番最後の処理で生成した構造体のメンバには何も保存されていません。いわゆる『空の構造体』が、リンクの最後にぶら下がっている状態です。

これは、whileループの構造が
次の構造体を先に生成しておき
続く繰り返し処理で構造体のメンバに値をセットする
という形になっているためです。

そこで、"p->next"で示される最後の構造体のメモリを解放し、そこにリンクの終端を示すNULLを代入します。
free(tmp->next);
tmp->next = NULL;

最後に、whileループの最初に保存しておいた先頭のポインタ"start"を返して終了します。
return (start);


リスト7:mainから呼び出されるmakelink関数
_CONV *  makelink(char *argv[])
{
  int    i, tabsize;
  _CONV  *p, *tmp, *start;

  /* 先頭はタブストップ幅 */
  tabsize = atoi(*argv++);

  /* argvがNULLになるまで解析を繰り返す */
  i = 0;    /* カウンタを初期化 */
  while((*argv != NULL)) {
    if (i == 0) {  /* 1回目だけ実行 */
      if ((p = malloc(sizeof(_CONV))) == NULL) {
        return(NULL);
      } else {
        start = p;
      }
    }
    p->tab = tabsize;
    strcpy(p->fname, *argv);   /* ファイル名をコピー */
    tmp = p;
    if ((p = malloc(sizeof(_CONV))) == NULL) {
      return(NULL);
    } else {
      tmp->next = p;
    }
    /* カウンタとオプション文字列を進める */
    i++;
    argv++;
  }
  /* お尻に余分な構造体が付いているので解放 */
  free(tmp->next);
  /* 終端のNULLをセット */
  tmp->next = NULL;
  /* 先頭の構造体のポインタを返す */
  return (start);
}

cleanmem~構造体のメモリを解放

プログラムの最後に、makelink関数で生成した_CONV構造体のリンクをたどって、malloc関数で確保したメモリを解放します。

引数は_CONV構造体のポインタで、main関数ではリンク先頭の構造体のポインタを渡します。

あとは、whileループで構造体のメンバnextをたどりながらfree関数で順次メモリを解放していくだけです。

リスト8:mainから呼び出されるcleanmem関数
void  cleanmem(_CONV *p)
{
  _CONV *tmp1, *tmp2;

  tmp1 = p;
  while (tmp1 != NULL) {
    tmp2 = tmp1->next;
    free(tmp1);
    tmp1 = tmp2;
  }
}


これで、プログラムが出来上がりました。今回のサンプルソースは"ex4301.c"という名前です。

プログラムの中枢となるtb2sp関数には一切変更がありませんが、それを利用する前後の処理は大きく変更されました。複数のファイルを扱うことで、繰り返し処理(ループ)が増えたことが最大のポイントです。

ソースを読みながら、プログラムの動作をイメージしてください。

【サンプルファイル実行の注意点】
お使いのOSによっては日本語の部分が文字化けする可能性がありますが、再コンパイルすることで回避できます。