文字と文字列

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
frontriver

文字と文字列

#1

投稿記事 by frontriver » 5年前

大学の課題で躓いています。下記は問題です。

入力できるバッファサイズを指定してバッファオーバーフローを起こさない文字列入力関数char *mygets(char *s, int size) を作成し、次のmain()関数で動作を確認しなさい。ここでsizeは用意した配列の大きさ(入力できる文字列の長さ+1)とします。

コード:

char *mygets(char *, int);
main()
{
  char buf[80];
  while(*mygets(buf, 80)) puts(buf); 
}

char *mygets(char * p, int size)
{
  関数の本体を書く
}
そして自分が書いたものはこちらです。

コード:

/*gp4.c*/
#include<stdio.h>
char *mygets(char *,int);
main()
{
  char buf[80];
  while(*mygets(buf,80)) puts(buf);
	
}

char *mygets(char *sp,int size)
{
  int c,i;
  char *p;
  i=0;
  p=sp;
  while((c = getchar()) != '\n'){
    *p = c;
    p++;
    i++;
    if(i == EOF) break;      
    if(i == size-1) break;
  }
  *p = '\0';
  return sp;
}

コード:

コンパイル結果
[peodbm04-( ~/C )-554]gcc gp4.c -o gp4
[peodbm04-( ~/C )-555]./gp4
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaa

[peodbm04-( ~/C )-556]
このようにEOFで終わらせて文字列を出したいのですが、enterを二回押さないと終わりません。
NULLをどのように使えばctrl+dで終わらせることができるのでしょうか?
ご教授よろしくお願いします。
課題の提出日は木曜日の23:59までなのでそこまでには終わらせたいと思っています。

かずま

Re: 文字と文字列

#2

投稿記事 by かずま » 5年前

EOF は負の値です。多くのコンパイラでは、#define EOF (-1)。
したがって、while の中で、常に正の値にしかならない i と比較
することは無意味です。

キーボード入力中、行頭で Ctrl+D を入力したとき、getchar() は
EOF を返します。だから、c = getchar() の c は '\n' と EOF の
両方と比較しないといけません。

行頭で Enter を入力した場合と、行頭で Ctrl+D を入力した場合
どちらも buf[0] == '\0' となり区別が付きません。

mygets を gets と同じ仕様にするなら、行頭で Ctrl+D を入力した
場合は、NULL を返すようにし、呼び出し側でもそれをチェックする
ようにしたほうがよいでしょう。

プログラムの 80 を 8 に変えたら、デバッグがしやすいですよ。
バグが取れたら戻せばよい。

蛇足ですが、件名は、「文字列入力関数の作成」のほうがよかった
ように思います。

frontriver

Re: 文字と文字列

#3

投稿記事 by frontriver » 5年前

かずまさん、いつも回答していただきありがとうございます。
回答した場所は大学だったので、家に帰って自宅のpcで行ってみました。

コード:

/*gp4.c*/
#include<stdio.h>
char *mygets(char *,int);
int main()
{
  char buf[8];
  while(*mygets(buf,8)) puts(buf);

}

char *mygets(char *sp,int size)
{
  int c,i;
  char *p;
  i=0;
  p=sp;
  while((c = getchar()) != '\n'||EOF){
    *p = c;
    p++;
    i++;
    if(i == size-1) break;
  }
  *p = '\0';
  return sp;
}

コード:

コンパイル結果
C:\MinGW\bin>gp4
aaaaaaaa
aaaaaaa
^C
C:\MinGW\bin>gp4
aaa



aaa







^C
C:\MinGW\bin>
配列の中身を8個にしてやってみました。
c = getchar()は'\n'とEOFで比べるため||を使いました。
しかし8個文字を打って、^D+Enter か enterをおすと7文字でsize通り出てきますが、改行でもう一つ文字が出るはずですが、出ない結果になりました。3文字の場合、実行すると空白が生まれ何度もenterキーを押すと3文字出てきます。
どのようにすればいいのか全く分かりません。
オーバーフローとはこの問題でいうと、8文字以上の文字列を作らないようにすることを言っているのですよね?
まずそこから理解不足です。
どうかご教授のほどよろしくお願いします。

かずま

Re: 文字と文字列

#4

投稿記事 by かずま » 5年前

(c = getchar()) != '\n'||EOF という式ですが、
||演算子の優先順位は低く、!= の評価が先です。

 getchar() で読み込んで c に代入した後の c の値が '\n' ではない (A)
  または
 EOF (B)

と解釈されます。

c が '\n' でないとき、(A) は真になり、(B) の評価は行わず、
whileループの中に入ります。

c が '\n' であるとき、(A) は偽になり、(B) を評価します。
(B) は c には無関係です。EOF の値は 0 ではないので、常に真です。
したがって、whileループの中に入ります。

c が EOF かどうかの判定をしていないどころか、
c が '\n' であるかどうかの判定も無効になります。

c がどんな値であっても whileループの中に入り、
i が size-1 になったとき、break でループを抜けます。

abcdefgh の 8個文字を打って Enter を押すと、
buf には "abcdefg" が入り表示されますが、その後
buf[0] = 'h', buf[1] = '\n' の状態になって、
次の入力を待っています。
ここで、123456789 の 9個の文字を打って Enter を押すと、
"h\n12345" の 7文字が表示されます。buf には、"6789" の
4文字がたまって、次の入力を待っています。

abcdefgh の 8個文字を打って Ctrl+D と Enter を押すと、
buf には "abcdefg" が入り表示されますが、その後
buf[0] = 'h', buf[1] = EOF, buf[2] = '\n' の状態になって、
次の入力を待っています。
あと 4文字入力すると表示されますが、EOF に相当する文字が
変な記号になるはずです。


次の 2つの式の意味をよく考えて試してみてください。

コード:

(c = getchar()) != '\n'  ||  c != EOF
(c = getchar()) != '\n'  &&  c != EOF

frontriver

Re: 文字と文字列

#5

投稿記事 by frontriver » 5年前

返信ありがとうございます。
||と&&で両方とも試してみました。

コード:

||の場合
[peodbm04-( ~/C )-503]gcc gp4.c -o gp4
[peodbm04-( ~/C )-504]./gp4
abcdefgh
abcdefg





h






^C
[peodbm04-( ~/C )-505]./gp4
abcdefgh
abcdefg





h






^C

コード:

&&の場合
[peodbm04-( ~/C )-508]gcc gp4.c -o gp4
[peodbm04-( ~/C )-509]./gp4
abcdefgh
abcdefg
h

[peodbm04-( ~/C )-510]./gp4
abcdefghabcdefg
h

||で行う場合、「または」と言う意味ですので片方だけあっていればwhileのループを続けてしまうのでenterを何度も押しても終了しないことがわかりました。
一方&&を使うと両方の条件を満たさなければならないので、enterやctrl+Dを実行すると、whileの中を抜けることができました。
まだ表面上しか理解しておらず、イマイチな感じではありますが、提出期限が限られているので&&の方で出してみようと思います。

frontriver

Re: 文字と文字列

#6

投稿記事 by frontriver » 5年前

&&の方でenterを押した後にctrl+Dを押すと綺麗に終了することができました。
これは文字を何も入れずにctlr+Dを押したのでwhileの中のEOFから抜けてbreakしたのでしょうか?そして文字列を入れた後にctrl+Dを押すと行替えされずに文字列がでました。enterは行替え+実行と言う意味でしょうか?そしてもう一度ctrl+Dを押すと行替えしてa[0]の場所に「h」がでました。これはどういうことでしょうか?

かずま

Re: 文字と文字列

#7

投稿記事 by かずま » 5年前

7の倍数(0を含む)の個数の文字を入力し Enter を押すと
終了してしまう不具合を修正しましょう。
これは、#2 で指摘したように '\n' と EOF のどちらで行入力
が終わったかの区別がつくようにすれば解決するでしょう。

かずま

Re: 文字と文字列

#8

投稿記事 by かずま » 5年前

frontriver さんが書きました:
5年前
&&の方でenterを押した後にctrl+Dを押すと綺麗に終了することができました。
これは文字を何も入れずにctlr+Dを押したのでwhileの中のEOFから抜けてbreakしたのでしょうか?そして文字列を入れた後にctrl+Dを押すと行替えされずに文字列がでました。enterは行替え+実行と言う意味でしょうか?そしてもう一度ctrl+Dを押すと行替えしてa[0]の場所に「h」がでました。これはどういうことでしょうか?
すみません。この #6 の投稿を見ずに、#5だけを見て #7 を送信して
しまいました。その現象が起きるソースを貼り付けてください。

frontriver

Re: 文字と文字列

#9

投稿記事 by frontriver » 5年前

コード:

[peodbm04-( ~/C )-501]cat gp4.c
/*gp4.c*/
#include<stdio.h>
char *mygets(char *,int);
main()
{
  char buf[80];
  while(*mygets(buf,80)) puts(buf);
    
}

char *mygets(char *sp,int size)
{
  int c,i;
  char *p;
  i=0;
  p=sp;
  while((c = getchar()) != '\n' && c != EOF){
    *p = c;
    p++;
    i++;      
    if(i == size-1) break;
  }
  *p = '\0';
  return sp;
}
 
[peodbm04-( ~/C )-502]gcc gp4.c -o gp4
[peodbm04-( ~/C )-503]./gp4
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aa
[peodbm04-( ~/C )-504]exit
exit

Process shell finished
こちらで大学に提出しました。
実行結果は文字列を81個入れてenterを押し79個の文字列と改行されて2個の文字列が出ました。その後ctrl+Dを押すと綺麗に終了できます。
一方で、このソースの中には記録されていませんが文字列を81個入れてenterではなく、ctrl+Dを入力すると行替されずに79個の文字列が出てしまい、あたかも160個の文字列のようになってしまいます。その後改行されて2個の文字列が出ました。
これは'\n'があるのでenterでも実行できると言うことなのでしょうか?

かずま

Re: 文字と文字列

#10

投稿記事 by かずま » 5年前

frontriver さんが書きました:
5年前
こちらで大学に提出しました。
提出してしまったんですね。
不具合がいくつかあると思うんですが。
・Enter だけを入力すると、終了してしまう。
・79文字入力してから Enter を入力すると、終了してしまう。
・78文字や、80文字、81文字だと Enter を入力しても終了せずに次の入力を待つ。
frontriver さんが書きました:
5年前
実行結果は文字列を81個入れてenterを押し79個の文字列と改行されて2個の文字列が出ました。その後ctrl+Dを押すと綺麗に終了できます。
Ctrl+D の代わりに Enter でも終了してしまいますよね。
いいんでしょうか?

#2 にも書きましたが、mygets の return sp; の前に
if (i == 0 && c == EOF) return NULL; を追加し、main で、
while (mygets(buf, 80)) puts(buf); とすれば
main で、Enter と Ctrl+D の区別ができるでしょう。

ここまでいいですか? 理解できますか?
frontriver さんが書きました:
5年前
一方で、このソースの中には記録されていませんが文字列を81個入れてenterではなく、ctrl+Dを入力すると行替されずに79個の文字列が出てしまい、あたかも160個の文字列のようになってしまいます。その後改行されて2個の文字列が出ました。
これは'\n'があるのでenterでも実行できると言うことなのでしょうか?
正確な情報をお願いします。
81文字と Ctrl+D の入力で、入力の 81文字と出力の79文字が連続して
表示されて改行もされますが、2個の文字はこの時点では表示されません。
次に、Enter を入力すると、2個の文字が表示され改行します。

それでは、行頭ではない Ctrl+D について詳しく説明します。

Enterキーは通常 0x0d ('\r') のコードですが、
どこかで 0x0a ('\n') に変換されて getchar がそれを返します。

Ctrl+D のコードは通常 0x04 ですが、
これが EOF に変換されるのではありません。
EOF は 1バイトの値ではなく、int の -1 です。

getchar() は getc(stdin) または fgetc(stdin) と同じです。
stdin は標準入力であり、FILE構造体へのポインタです。
FILE構造体の中には入出力をバッファリングするバッファがあります。

標準入力バッファが空の時に getchar が呼ばれると、
まずそのバッファに文字を入力するために
システムコールの read を呼び出します。
標準入力は端末ドライバに接続されていて、
OS内のキーボード入力バッファが使用されます。

「標準入力バッファ」と「キーボード入力バッファ」の
2個所でバッファリングが行われているのです。

キー入力は、まずキーボード入力バッファにため込まれます。
だから、Backspace で入力文字の削除もできるのです。
キー入力がすぐに getchar に反映されるのではありません。

Enterか、Ctrl+D が入力されたときに、キーボード入力バッファの
文字列が標準入力バッファに転送されて、read の処理が完了します。
Enter は '\n' の文字としてそこに入っていますが、Ctrl+D は
文字としては入っていません。

行頭での Enter入力は、'\n' 1文字が転送され、read は 1 を返します。
行頭での Ctrl+D入力は、読み込み文字数 0 が read の返す値となります。
getchar は、read による読み込み文字数が 0 の場合に EOF を返すのです。

行頭ではない Enter入力は '\n' も含めた 1行分の文字列が転送されます。
行頭ではない Ctrl+D入力は '\n' を含まない 1行分の文字列が転送されます。
いずれも場合もバッファには文字があるので、getchar はそれを返し、
EOF を返すことはありません。

標準入力バッファが空でない時に getchar が呼ばれると、
バッファ内の文字列の先頭の文字が返されるだけです。

何度も getchar が呼ばれて、バッファが空になると、
次の getchar では、readシステムコールを呼び出し、
キー入力待ちになるのです。

さて、81文字と Ctrl+D を入力すると、81文字が標準入力バッファに
転送されます。getchar は 81回実行されますが、
79回実行したところで、mygets は i が size-1 になるので
while ループを break し、main に返って、puts(buf) で
その 79文字と改行が出力されるのです。

また mygets が呼ばれて、getchar を 2回実行したところで
buf[0] と buf[1] にその文字が入ります。
次の getchar は標準入力バッファが空なので、read を呼び出し、
キーボード入力バッファに文字をため込むためにキー入力待ちになります。

ここで Enter を入力すると、それはキーボード入力バッファに入り、
標準入力バッファに転送され、getchar が '\n' を返し、
mygets は終了し、main で 2文字を改行を出力します。

ちょっとややこしいですが、理解していただけますか?

かずま

Re: 文字と文字列

#11

投稿記事 by かずま » 5年前

#10の説明は Unix (Linux など) の端末ドライバの説明なので、
Windows上で Unix の環境を可能な限り実現しようとするCygwin
でもそのまま通用すると思うのですが、質問者の環境は MinGW
でしたね。
したがって、この説明では解決できない現象が出るかもしれません。

frontriver

Re: 文字と文字列

#12

投稿記事 by frontriver » 5年前

かずまさん、詳しく書いて頂きありがとうございます。
つまるところ、キーボード入力バッファと標準入力バッファの2つがあり、キーボードで入力された文字列はキーボード入力バッファに貯められるので、文字の削除が可能であり、enterやctrl+Dを入力する事により、キーボード入力バッファに貯められた文字列が転送され、標準入力バッファに送られると言う事なんですね。そして、81文字入力された文字列はsize-1により79文字目でwhileを抜け、breakされ、mainに戻りputsされ、2週目でbuf[0],[1]に残りの2文字が入る訳ですね。最後に入力待ちで行頭にenterを入力すると'\n'を返し改行するんです
ね。

全然理解出来てなかったのを、ここまで丁寧に教えて頂いたお陰で頭の中がスッキリしました。本当にありがとうございます。

FILF構造体の所がイマイチ理解できませんでしたが、後でネットで調べてみます。

frontriver

Re: 文字と文字列

#13

投稿記事 by frontriver » 5年前

MinGWは大学で使っているコンパイラと比べると少し扱いにくいと思っていました。
プログラムを実行する際、ctrl+Dとenterを押すんですが、ctrl+Zとenterを押した方が、大学で使っているコンパイラと同じ動きをしてくれる時が多いです。
Cygwinに変えた方がいいでしょうか?

返信

“C言語何でも質問掲示板” へ戻る