C言語関係掲示板

過去ログ

No627 K&R アンサーブックの演習1−18について

[戻る] [ホームページ]
No.6404

K&R アンサーブックの演習1−18について
投稿者---Gen(2003/05/15 18:12:29)


K&Rアンサーブックの演習1−18について、気になる動作を見つけたので
ご報告いたします。
この演習は、行の終わりにある連続したタブとブランクを取り除き、
さらに、ブランクだけの行を削除するプログラムです。
K&Rアンサーブックに掲載されていたプログラムでは、
改行なしでNull文字だけの行については、コアダンプとしてしまいます。
例えば、
abc \t\n\0
efg\t \n\0

は問題なく動作しますが、
abc \t\n\0
efg\t \0
ではコアダンプとしてしまいます。
以下の様にすれば問題なく動くのですが、アンサーブックは間違っているのでしょうか?

#include <stdio.h>
#define MAXLINE 1000

int rm(char line[]);
int getline(char line[], int maxline);

main()
{
  int i, len, flag;
  char line[MAXLINE];
  
  /* line[len - 1] is '\n', line[len] is '\0' */
  while (getline(line, MAXLINE) > 0)
    if (rm(line) > 0)
      printf("%s", line);

  return 0;
}

int rm(char s[])
{
  int len, nl;

  len = nl = 0;
  while (s[len] != '\0') {
    if (s[len] == '\n')
      ++nl;
    ++len;
  }
  if (nl == 0)
    len = len - 1;
  else
    len = len - 2;
  while (len >= 0 && (s[len] == ' ' || s[len] == '\t'))
    --len;
  if (len >= 0)
    if (nl != 0)
      s[++len] = '\n';
      s[++len] = '\0';

  return len;
}

/* getline: s に行を入れ, 長さを返す */
int getline(char s[], int lim)
{
  int c, i, j;

  j = 0;
  for (i = 0; (c = getchar()) != EOF && c != '\n'; ++i)
    if (i < lim - 2) {
      s[j] = c;
      ++j;
    }
  if (c == '\n') {
    s[i] = c;
    ++i;
    ++j;
  }
  s[j] = '\0';

  return i;
}




No.6419

Re:K&R アンサーブックの演習1−18について
投稿者---kikk(2003/05/15 21:27:59)


ども。


アンサーブックを持ってないので、元はどうだったのかはわかりませんが、
たぶん、行末の改行文字の有無が行の成立に関わるかどうかが問題なのだと
思います。最終行以外の行で、このことを考える必要が無いことは明らか
ですが、最終行は問題になります。C言語自体では、テキストストリームの
最終行の改行文字の要求は処理系定義となっています。

UNIXのテキストは一般に最終行に末尾改行があることを仮定しています。
C言語が標準的な実行環境としてUNIXを想定していると思われるフシがある
ことを考えると、間違っていると言い切れるかは微妙なところです。

ですが、最終行の末尾改行はあっても無くてもいいということになっている
以上、処理系を想定しない/できないのであれば、改行の有無に依存しない
コードのほうがいいコードだと思います。


では。

No.6422

Re:K&R アンサーブックの演習1−18について
投稿者---Gen(2003/05/15 22:35:08)


ご返答ありがとうございます。
C言語を使うときは、Unixを使っているのですが

>UNIXのテキストは一般に最終行に末尾改行があることを仮定しています。
>C言語が標準的な実行環境としてUNIXを想定していると思われるフシがある

と言う事は知りませんでした。為になります。
Unix限定ということで、間違いではないということですね。ありがとうございます。
ちなみに、アンサーブックには以下のように書かれていました。
また、以下のプログラムでは rm という関数になっていますが、
アンサーブックではremove となっていました。
そのままでは、コンパイルできないためrm とリネームしました。

#include <stdio.h>
#define MAXLINE 1000

int rm(char s[]);
int getline(char line[], int maxline);

main()
{
  char line[MAXLINE];
  
  while (getline(line, MAXLINE) > 0)
    if (rm(line) > 0)
      printf("%s", line);

  return 0;
}

int rm(char s[])
{
  int i;

  i = 0;
  while (s[i] != '\n')
    ++i;
  --i;
  while (i >=0 && (s[i] == ' ' || s[i] == '\t'))
    --i;
  if (i >= 0) {
    ++i;
    s[i] = '\n';
    ++i;
    s[i] = '\0';
  }
  return i;
}

/* getline: s に行を入れ, 長さを返す */
int getline(char s[], int lim)
{
  int c, i, j;

  j = 0;
  for (i = 0; (c = getchar()) != EOF && c != '\n'; ++i)
    if (i < lim - 2) {
      s[j] = c;
      ++j;
    }
  if (c == '\n') {
    s[i] = c;
    ++i;
    ++j;
  }
  s[j] = '\0';

  return i;
}


No.6442

Re:K&R アンサーブックの演習1−18について
投稿者---かずま(2003/05/16 13:45:13)


> ちなみに、アンサーブックには以下のように書かれていました。

本当に、アンサーブックにそう書かれているんですか。
その getline はバグっています。

lim を超える入力があった場合、
s[0]〜s[lim-3] には入力文字が入りますが、
s[lim-2] には何も入らず、s[lim-1] には '\0' が入り、
もっとひどいことに、s[i] (i > lim) に '\n' が入ってしまいます。

この演習1-18 の前のページに正しく動く getline があるのに
それを使わず、こんな間違いの getline を掲載しているのでしょうか。

No.6443

Re:K&R アンサーブックの演習1−18について
投稿者---かずま(2003/05/16 14:03:59)


私なら、演習1-18 の解答は次のようにします。
#include <stdio.h>

#define BUFSIZE  1000

int main(void)
{
    int c, i, j = 0;  char buf[BUFSIZE];

    while ((c = getchar()) != EOF)
        if (c == ' ' || c == '\t') {
            if (j < BUFSIZE)
                buf[j++] = c;
        } else {
            if (c != '\n')
                for (i = 0; i < j; i++)
                    putchar(buf[i]);
            j = 0;
            putchar(c);
        }
    return 0;
}


No.6452

Re:K&R アンサーブックの演習1−18について
投稿者---Gen(2003/05/16 17:06:34)


アンサーブックには上記のように掲載されており、getlineを少し変更したと書いてあります。
また、limを超える入力があった場合というのは、もともとのgetlineでも正しく処理できないのでは?
lim(MAXLINE)以下の入力しかないと仮定した場合は、
こちらの方がよいかと思うのでは?

ちなみに、アンサーブックには以下のように変更した理由を掲載しています。

if (i < lim - 2)
このif文で配列にまだ余裕があることを判断しています。
もとのfor文の判断では
i < lim - 1
でした。これは配列sがlim個の要素を持っているので、最大の添字が、
lim - 1
であるためでした。しかしこの解答では、
i < lim - 2
として、次にくるかもしれない改行文字、
s[lim - 2] = '\n'
と、文字列の終わりを示すヌル文字のための場所を確保しています。
s[lim - 1] = '\0'



No.6455

Re:K&R アンサーブックの演習1−18について
投稿者---かずま(2003/05/16 17:50:21)


> また、limを超える入力があった場合というのは、もともとのgetlineでも
> 正しく処理できないのでは?

いいえ、正しく処理できます。次のテストプログラムで試してみれば分かります。
K&R を持っていない人にも納得できるでしょう。
#include <stdio.h>

/* getline: s に行を入れ, 長さを返す */
int getline(char s[], int lim)
{
  int c, i;

  for (i=0; i<lim-1 && (c=getchar())!=EOF && c != '\n'; ++i)
      s[i] = c;
  if (c == '\n') {
    s[i] = c;
    ++i;
  }
  s[i] = '\0';
  return i;
}

int main(void)
{
    char buf[16] = "0123456789";
    int n = getline(buf, 5);
    int i;
    for (i = 0; i < 10; i++)
        printf(" %02x", buf[i]);
    printf("\nn = %d\n", n);
    return 0;
}

> ちなみに、アンサーブックには以下のように変更した理由を掲載しています。
>
> if (i < lim - 2)
> このif文で配列にまだ余裕があることを判断しています。

このために、No.6442 で説明したように s[lim-2] に何も入らないバグが生じます。

もとの for文の i < lim - 1 でループを抜けるときは、i の値は lim-1 に
なっていて、この場合、c は '\n' ではありませんから、s[lim-1] にめでたく
'\0' が入ります。

c に '\n' が入るときは、i は lim-2 以下なので、十分余裕があります。

No.6561

Re:K&R アンサーブックの演習1−18について
投稿者---Gen(2003/05/19 19:53:57)


なるほどです。ちなみに私の言った正しく処理するというのは、
limを超える長さに対しても、超えた文字列も含めすべての文字列を表示できるかという意味でした。limを超えても超えた文字列を除いた文字列を表示できれば正しく処理できているという解釈でよいのですね。

 
> 私なら、演習1-18 の解答は次のようにします。

空白行を削除する機能が抜けていました。

#include <stdio.h>

int main(void)
{
    int c, i;
    int j = 0;       /* 連続するスペースとタブの個数         */
    int k = 0;       /* 行内のスペースとタブ以外の文字の個数 */
    char buf[1024];  /* 連続するスペースとタブの保存領域     */

    while ((c = getchar()) != EOF)
        if (c == ' ' || c == '\t') {
            if (j < sizeof buf)
                buf[j++] = c;
        } else if (c == '\n') {
            if (k != 0)
                putchar(c);
            j = k = 0;
        } else {
            for (i = 0; i < j; i++)
                putchar(buf[i]);
            putchar(c);
            j = 0;
            k++;
        }
    return 0;
}


ちなみに、演習1-18以前ではsizeofは出てきません。1-18に出てくる範囲内でプログラムすると、ここの条件は j < 1024 でいいのでしょうか?


No.6563

Re:K&R アンサーブックの演習1−18について
投稿者---かずま(2003/05/19 21:30:18)


> ちなみに、演習1-18以前ではsizeofは出てきません。1-18に出てくる範囲内
> でプログラムすると、ここの条件は j < 1024 でいいのでしょうか?
No.6443 のように  #define BUFSIZE  1024   にしてください。
それから、宣言時の初期化も第2章からですね。

No.6462

Re:K&R アンサーブックの演習1−18について
投稿者---かずま(2003/05/16 20:34:03)


> 私なら、演習1-18 の解答は次のようにします。

空白行を削除する機能が抜けていました。
#include <stdio.h>

int main(void)
{
    int c, i;
    int j = 0;       /* 連続するスペースとタブの個数         */
    int k = 0;       /* 行内のスペースとタブ以外の文字の個数 */
    char buf[1024];  /* 連続するスペースとタブの保存領域     */

    while ((c = getchar()) != EOF)
        if (c == ' ' || c == '\t') {
            if (j < sizeof buf)
                buf[j++] = c;
        } else if (c == '\n') {
            if (k != 0)
                putchar(c);
            j = k = 0;
        } else {
            for (i = 0; i < j; i++)
                putchar(buf[i]);
            putchar(c);
            j = 0;
            k++;
        }
    return 0;
}