質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

3回答

10433閲覧

文字列をバッファに格納するプログラム

aiueo12345

総合スコア41

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2019/06/20 10:03

編集2019/06/20 10:05

前提・実現したいこと

バッファに文字列を格納するプログラムをCで書きました。
以下にそのプログラムを示します。

#include <stdio.h> #define BUFSIZE 24 struct buffer { char store[BUFSIZE]; int head , tail; }; int put_str(struct buffer *buf, char *str) { int i = buf->tail; while (i - buf->head < BUFSIZE) { buf->store[i++ % BUFSIZE] = *str; if (*str == '\0') { buf->tail = i; return 1; } else { str++; } } return 0; } int get_str(struct buffer *buf, char *dest) { int i = buf->head; if (i == buf->tail/BUFSIZE ) return 0; do { *dest = buf->store[i] ; } while ( buf->store[i++] != '\0'); buf->head = i; return 1; } int main() { struct buffer buf; char dest; int i; buf.head = buf.tail = 0; for(i = 0; i < BUFSIZE; i++) buf.store[i] = '\0'; put_str(&buf, "ten"); put_str(&buf, "six"); put_str(&buf, "three"); put_str(&buf, "four"); get_str(&buf, &dest); while(dest != '\0') printf("%c", dest++); puts(""); return 0; }

格納文字列はバッファ領域を超えないとします。

発生している問題

main関数内で文字列を表示させようと試みたところ、上手くいきませんでした。
コンパイルし実行することはできたのですが、何も表示されませんでした。

おそらくmain関数内だけを変更すれば上手くいくと思います。

get_str関数に与える二つ目の引数と、main関数内のprintfに問題がありそうな気がするのですが、どうでしょうか。

get_strの二つ目の引数としてchar型の配列の先頭アドレスを渡してみたり、char型のポインタを渡してみたりしたのですが、上手くいきませんでした。

どのように改善すればよいでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

もうすべてがぶっ壊れているといって過言ではありません。

c

1 while (i - buf->head < BUFSIZE) { 2 buf->store[i++ % BUFSIZE] = *str; 3 if (*str == '\0') { 4 buf->tail = i; 5 return 1; 6 } else { 7 str++; 8 } 9 }

文字列のコピーを自分でループで書くのは完全に誤った発想です。文字列のコピーは常にmemcpy関数によって行われるべきです。短く、高速に、安全に書けます。Don't Repeat Yourself!ちなみにstrcpyとかsprintfとかstrcatとかは安全ではありませんから使うべきではありません。

i++ % BUFSIZEというのは謎で、これだけ見るとあたかもリングバッファのように見えてくるのですが、他の部分のコードから見るにそんなことはないただのキュー構造ですから、剰余を取るのは意味不明です。buffer overrun対策は文字列のコピーの前にバッファの残りの容量とコピーしようとする文字列の長さから計算して弾くべきです。

c

1int get_str(struct buffer *buf, char *dest)

はい、おかしいですね。文字列を引数dest経由で返したいと言うのが意図のはずですが、ここで関数が呼び出されたときにdestが指し示している領域の大きさをget_strが知る手段がありません。この時点でぶっ壊れています。引数を足して大きさも渡しましょう。

c

1 do { 2 *dest = buf->store[i] ; 3 } while ( buf->store[i++] != '\0');

そもそもdestを動かしていないのでどんなにループが回ろうとも同じ場所に書き込みます。ぶっ壊れていますね!

というかそもそも文字列のコピーをループで書こうとするのは誤りです。予め長さの検査をした上でmemcpyを使いましょう。自分でループ書くより短く、高速に、安全に書けます。Don't Repeat Yourself!

c

1int main() { 2 struct buffer buf; 3 char dest; 4 //中略 5 get_str(&buf, &dest);

ここで変数destの型に注目しましょう。まさかのchar型です。一体どうやって文字列を格納するんでしょうね?実にぶっ壊れています。適当な大きさのメモリー領域を確保する必要がありますね(配列でもいいしmallocのようななにかでもいい)

c

1 while(dest != '\0') 2 printf("%c", dest++);

文字列を出力するのに、わざわざ一文字ずつ表示してループを回すというじつにクレイジーなコードです。素直に%sを使って出力しましょう。


というわけで面白いくらいぶっ壊れまくっています。疲れたので言及しませんが、きっとこれ以外にもぶっ壊れているところがあるでしょう。

投稿2019/06/20 10:49

yumetodo

総合スコア5850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aiueo12345

2019/06/20 12:16

ご回答ありがとうございます。 このような場合はmemcpyを使うとよいのですね。 もし今後書くことがあれば使うようにします。 また、strcpyが安全でないというのは知らなかったため、勉強になりました。ありがとうございます。 しかし、今回はこのようなプログラムである必要があるのです。 実はこれはある試験問題のプログラムで、以下の(A)~(D)を埋める問題でした。 (main関数は自分の解答の正誤を確認するために必要だったため追加しました) ``` #define BUFSIZE 24 struct buffer { char store[BUFSIZE]; int head , tail; }; int put_str(struct buffer *buf, char *str) { int i = buf->tail; while (i - buf->head < BUFSIZE) { buf->store[i++ % BUFSIZE] = *str; if (*str == ’\0’) { buf->tail = i; return 1; } else { str++; } } return 0; } int get_str(struct buffer *buf, char *dest) { int i = buf->head; if (i == (A) ) return 0; do { *dest = (B) ; } while ( (C) != ’\0’); (D) = i; return 1; } ``` ですので、使用関数等についてはご容赦ください。 『i++ % BUFSIZE』については、yumetodoさんの予想通りリングバッファだからだと思います。今回はheadもtailも0で初期化しているため、わかりにくくなってしまいました。 すみません。 『int get_str(struct buffer *buf, char *dest)』について、引数は上記の理由で触れないため、このままでお願いします。 たしかにdestが指し示す先の容量は知ることができませんが、BUFSIZE以下の長さの文字列を格納することが前提であれば、dest側でBUFSIZE分とっておけば問題ないかもしれません。 get_str内でdestを動かしてない点についてはおっしゃる通りだと思います。 問題が間違っているような気がします。 最後の出力については『printf("%s", dest);』に変更しました。 おかげさまで思った通りに実行することができました。 ありがとうございました。
aiueo12345

2019/06/20 12:16

インデントがない読みにくい返信ですみません。 返信でインデントをうつ方法がわからず、打ったのですがなくなってしまいました。
yumetodo

2019/06/20 12:38

試験問題作った人はCをまともに書いたことないんじゃないですかね。 というかこれでリングバッファってまじですか?get_str書いた人とput_strかいた人が別人だってほうがまだ納得行く。 後学のために、これどこの試験ですか?専門学校?大学?なんかの資格?それとも?(具体名はいいのでどういう区分かをですね -- strcpyが安全でないについては補足を。 strcpyを安全に使うことは出来ますが、そのために事前検査をするともれなく文字列の大きさがわかっている必要があるので、strcpyでなくmemcpyのほうが効率的ということです。 sprintfは数値→文字列の変換のためなら、数値型の大きさから必要なバッファの大きさを計算できなくもないので使うことは出来ます。文字列の結合は本当はsprintf使うと楽なんですが、安全に使うのはだいぶ面倒でコード量がmemcpyするのと大差ないなんてこともあるので、つらいです。 結論は文字列操作するならJavascriptとかC#とかとにかくCではない言語がいいですよ!ってことです
aiueo12345

2019/06/20 13:46

この問題は筑波大学コンピュータサイエンス専攻の院試の過去問です。 (ちなみに私が院試を受けるのは数年後です) 他の問題も解いていて、なかなか意地の悪い問題があるように感じています。 strcpyについて、そういうことだったんですね。 memcpyでは引数として確保しているメモリを明示するという点で安全性が高いのですね。 strcpyの方が使い慣れていますがmemcpyを使うようにしていきたいと思います。
yumetodo

2019/06/21 01:33

実際C標準関数の安全性というかbuffer overrunの可能性にめちゃくちゃうるさいMSVCはmemcpy以外の大抵の文字操作系関数について警告を出してくるのですが、実際安全に使えないor他の関数のほうが効率的なので仕方ない。 C11でMSVC独自拡張だった_sが末尾につく関数群がoptionalな形で標準入りしたのですが、結局_sをつけるんじゃなくてmemcpyしないとだめだよね、みたいな話だと思うんですがやっぱ取り除こうなんて話もあるので・・・。 筑波大学・・・、ひええぇ。
aiueo12345

2019/06/21 09:53

memcpy以外には警告を出してくる環境もあるのですね。 詳しいことはよく分かりませんが、こういった話があると知れたのはいい経験になりました。 ありがとうございます。
guest

0

回答ではないです。すみません。
このプログラムって要は、memsetやstrcat、printfでパパッとできることを自力でやってみた、もしくはやってみたいという主旨でよいのでしょうか。

c

1#include <stdio.h> 2#include <string.h> 3 4int main(void) 5{ 6 char store[BUFSIZE]; 7 8 memset(store, 0x00, sizeof(store)); 9 10 strcat(store, "ten"); 11 strcat(store, "six"); 12 strcat(store, "three"); 13 strcat(store, "four"); 14 15 printf("%s", store); 16 17 return 0; 18} 19

投稿2019/06/20 11:13

ttyp03

総合スコア16996

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

majiponi

2019/06/20 11:23

これだとtensixthreefourと出力されます。 おそらく質問者さんは、最初のget_strでten、次のget_strでsixと出力されることを望んでいらっしゃると思います。
ttyp03

2019/06/20 11:37

そうなんですかね? bufが動的に確保されているわけではないので、一次元のbuf構造体内での処理にしか見えませんでした。 store配列をNULL区切りで格納してってることでしょうか。 まあ謎な部分が多いのでどのような実装をしたいのか仕様を明確にしてもらいたいですね。 あと私は質問者さんに聞いているだけなので、マイナス評価は心外です。 majiponiさんじゃなかったらすみません。
majiponi

2019/06/20 11:54

なるほど、それは失礼しました。(質問への追記要請でやればいいような気もしますが) 確かに一番の問題点は、仕様が詰まってないことですよね…
ttyp03

2019/06/20 11:56

いえ、すみません。 コードを提示するのには質問の追記要請だと見づらいので回答という形にしてしまいました。
aiueo12345

2019/06/20 12:18

混乱を招いてしまい申し訳ないです。 私としてはmajiponiさんにおっしゃっていただいたとおり、格納した文字列一つずつを取り出したいと考えております。
ttyp03

2019/06/20 12:21

なるほど。 ということはやはりNULL区切りで文字列を格納したい、ということでしょうか。 そういった具体的な仕様を提示しないまま、できない、わからない、ということを質問しないほうがよいですよ。
aiueo12345

2019/06/20 12:24

おっしゃる通り、ナル文字で区切るということです。 質問に不備が多く、すみませんでした。
guest

0

ベストアンサー

問題点1
get_str関数で返したいのは文字列(charの配列)だが、関数に渡しているのはchar型の変数である。main関数内部のdestを受け取り用の配列にする必要がある。

問題点2
get_str関数が文字列を返す仕様になっていない。
ポインタを更新せず、同じ領域を上書きしている。

問題点3
get_str内部の「割り算」の意味は?

追記:私が書く場合

C

1int put_str(struct buffer *buf, const char *str) { 2 int i, nFree = BUFSIZE - (buf->tail - buf->head); 3 if(nFree == 0) return 0; 4 for(nFree--; nFree > 0 && *str; nFree--){ 5 buf->store[buf->tail++ % BUFSIZE] = *str++; 6 } 7 buf->store[buf->tail++ % BUFSIZE] = '\0'; 8 return !*str; 9} 10 11int get_str(struct buffer *buf, char *dest, int size) { 12 if(buf->head == buf->tail) return 0; 13 if(size == 0) return 0; 14 for(size--; size > 0 && buf->store[buf->head % BUFSIZE]; size--){ 15 *dest++ = buf->store[buf->head++ % BUFSIZE]; 16 } 17 *dest = '\0'; 18 if(buf->store[buf->head % BUFSIZE]) return 0; 19 buf->head++; 20 return 1; 21} 22 23 24int main() { 25 struct buffer buf; 26 char dest[BUFSIZE]; 27 int i; 28 29 buf.head = buf.tail = 0; 30 for(i = 0; i < BUFSIZE; i++){ 31 buf.store[i] = '\0'; 32 } 33 34 put_str(&buf, "ten"); 35 put_str(&buf, "six"); 36 put_str(&buf, "three"); 37 put_str(&buf, "four"); 38 39 get_str(&buf, &dest, sizeof(dest) / sizeof(dest[0])); 40 puts(dest); 41 42 return 0; 43}

携帯から打っているのでミスがあるかもしれませんが、こんな感じです。

投稿2019/06/20 10:31

編集2019/06/20 12:27
majiponi

総合スコア1720

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aiueo12345

2019/06/20 12:00

ご回答ありがとうございます。 問題点1について、たしかにおっしゃる通りです。 問題点2について、やはりこれではまずいですよね。 実はこのプログラムはある試験問題で、get_str内の数か所を埋めて完成させる問題でした。 (put_strは穴あきはなく、main関数は自分の解答の正誤を確認するためにあとから自分で追加しました) このdestの部分は穴あきになっておらず、変更のしようがなかったのでそのままにしてしまいました。 問題点3について、この割り算ではheadとtailがバッファの同じ部分を表しているかどうかを調べるための計算です。 tailがbUFSIZEを超えていてもBUFSIZEで割ることで、配列storeの添え字に対応させられるようにしたつもりでしたが、正しくは『%』でした。 あとは他回答者様のご指摘通りに修正したら上手くいきました。 ありがとうございます。 ちなみに、試験問題というのは以下の(A)~(D)を埋めるものでした。 ``` #define BUFSIZE 24 struct buffer { char store[BUFSIZE]; int head , tail; }; int put_str(struct buffer *buf, char *str) { int i = buf->tail; while (i - buf->head < BUFSIZE) { buf->store[i++ % BUFSIZE] = *str; if (*str == ’\0’) { buf->tail = i; return 1; } else { str++; } } return 0; } int get_str(struct buffer *buf, char *dest) { int i = buf->head; if (i == (A) ) return 0; do { *dest = (B) ; } while ( (C) != ’\0’); (D) = i; return 1; } ```
aiueo12345

2019/06/20 12:01

インデントも打ったはずなのに無くなっていますね。 たいへん読みにくいものですみません。
majiponi

2019/06/20 12:10

問題ないのです、コメントの中ではなぜかコードが記述できないテラテールの仕様が悪いのです。この後で私が書いたらこうなる、というのを回答編集して記載します。
aiueo12345

2019/06/20 12:27

そうでしたか、そういう仕様なのですね。 ちなみに、問題文による条件は以下の通りでした。 ・get_strはバッファに格納された文字列を一つ返す関数 ・バッファに何も格納されていなければ0を返し、それ以外の場合はheadから始まる文字列をdestの指すメモリ領域にコピーし1を返す ・(A)~(D)空欄内では関数呼び出しはしない
majiponi

2019/06/20 12:47 編集

敢えて答えるなら A. buf->tail B. buf->store[i++ % BUFSIZE] C. *dest++ D. buf->head といったとこでしょうか。
aiueo12345

2019/06/20 13:31

追記に関してまだじっくりは読めていませんが、コンパイルしてみたところ、余計なput_str内のiと、main関数内のget_strの二つ目の引数の&を削除すると上手くいきました。 スマホからこれを打ったというのがすごすぎます。 時間のある時にじっくり読んで勉強させていただきます。 また、問題の解答ありがとうございます。 そのようにdest++をしてやればよかったのですね。すっきりしました。 ただ、一つ気になるのが、Aです。 tailがBUFSIZEを超えて大きくなることを考えると、『buf->tail % BUFSIZE』ではないでしょうか?
majiponi

2019/06/20 15:33 編集

いえ、これは剰余をとってはいけません。 書き込み、読み取りを繰り返すうちに、headはBUFSIZEをいずれは超えます(その場合、iも剰余を取る必要が出てきます)。全て読み取って空の状態では、headとtailは完全に一致するので、わざわざ剰余を取る必要はありません。 また、最初にBUFSIZE-1文字の文字列を保存した場合、tailはBUFSIZEになりますが、この場合は空ではありませんが、剰余を取ると上手く検出できません。
aiueo12345

2019/06/20 15:51

たしかにおっしゃる通りでした、すみません。 例として出していただいたものは、最初にBUFSIZE文字の文字列を保存した場合だと思いますが、そうであれば納得できました。 丁寧に教えてくださりありがとうございました。 最後に一つ、質問の仕方についてご教授いただけますでしょうか。 今後もテラテイルでこのような問題を質問しようかと思うのですが、『正解を教えてください』や『自分の解答はこうこうなのですが合っていますか?』というのは無責任だと思います。 ですが、今回のように色々混乱させてしまうこともあると思うのですが、どのような質問が適しているでしょうか? 特に、自分で解答できれば実行してみて確かめられますが、わからない部分があった場合は難しいです。 そもそも、テラテイルでそのような質問をするのが間違いでしょうか?
majiponi

2019/06/20 22:04

大学院の過去問を解いてみたが、自分の解答ではなぜ上手く動かないのか理解できない、自分はこう考えてこの解答にしたが、どのように考える必要があるのか、ということを聞くのがよいと思います。特に、考え方・理由を言語化する過程は自らを客観的に分析し直すために有効です。
aiueo12345

2019/06/20 23:37

自分の答えだけでなく、思考も示すことが大切なのですね。 本当に色々とありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問