- ベストアンサー
C言語で可変長から固定長に変換する方法とは?
- C言語で可変長から固定長に変換する方法について教えてください。
- 可変長のファイルを固定長にそろえるためのプログラムがうまく動作しないです。
- fprintfを使った際にファイルの長さが40に揃わない問題が発生しています。
- みんなの回答 (9)
- 専門家の回答
質問者が選んだベストアンサー
>while(fgets(buf,REC,fp)!= NULL){ >len = strlen(buf); >printf("レコード長 :%d\n",len); >} >結果は >レコード長 :39 >レコード長 :2 よくあるミスです。 fgetsの2番目の引数は「文字列の終端文字であるヌル文字を格納する分を含んだ、バッファの実サイズ」を指定する事になっています。 そして、困った事に「改行文字は、文字列の文字数に含み、改行文字も読み込む」のです。 では、質問者さんのプログラムを検証してみましょう。 while(fgets(buf,REC,fp)!= NULL){ RECは40です。そのため、fgetsは「bufが一杯になる39文字までを読んで、読み込みを打ち切り、末尾にヌル文字を付加」します。 len = strlen(buf); bufには「39文字+終端のヌル文字」が入っているので、strlenは終端のヌル文字の手前にある文字数の「39」を返し、lenは39になります。 printf("レコード長 :%d\n",len); printfは「レコード長 :39+改行」を表示します。 while(fgets(buf,REC,fp)!= NULL){ RECは40です。そのため、fgetsは「さっき読み残した1文字と、改行文字を読んで打ち切り、末尾、つまり改行文字の後にヌル文字を付加」します。 len = strlen(buf); bufには「読み残した1文字+改行文字1文字+終端のヌル文字」が入っているので、strlenは終端のヌル文字の手前にある文字数の「2」を返し、lenは2になります。 printf("レコード長 :%d\n",len); printfは「レコード長 :2+改行」を表示します。 結果、質問者さんのプログラムは レコード長 :39 レコード長 :2 を表示する事になります。 これは「意図した結果ではないが、fgets()関数の仕様通りの動作」であり「正常に動作している」のです。 単に「意図通りの引数を指定しなかった為、意図した動きをしなかっただけ」なのですね。 そういう訳で「40文字+改行1文字+終端ヌル1文字」で、バッファは42バイト必要で、fgetsには「42文字あるバッファ」と「42」を指定しなければならないのです。 レコード長の定義として「#define REC 40」と定義しているのなら、以下のように書かねばなりません。 #define REC 40 char buf[REC + 2] /* 改行文字と終端ヌル文字も格納できるように、レコード長より2文字分大きいバッファを用意しなければならない */ (途中省略) while(fgets(buf,siziof(buf),fp)!= NULL){ /* fgetsの第2引数に定数は指定しない事。必ず「sizeof(指定したバッファ)を指定すること */ len = strlen(buf); if (buf[len - 1] == '\n') buf[--len] = '\0'; /* 末尾に改行があるなら、末尾の改行を終端文字ヌルで上書きして取り去り、その分、lenを1減らす */ printf("レコード長 :%d\n",len); }
その他の回答 (8)
- chie65536(@chie65535)
- ベストアンサー率44% (8766/19892)
追記な訂正。 >fprintf(fpt,40,"%-40s\n",buf); は fprintf(fpt,"%-40s\n",buf); の誤り。度々すいません。
- chie65536(@chie65535)
- ベストアンサー率44% (8766/19892)
蛇足な追記。 今回のように「40文字の幅に左詰めでファイルに書く」のではなく「40文字以下の、n文字の空白文字の文字列」が欲しいなら char * str = (char *)malloc(n+1); memset(str, ' ', n); str[n] = '\0'; (中略) free(str); でも構わないけど、もっと簡単に char *str; str = " (ここに空白を40文字並べる:注参照) " + 40 - n; でOK(但し、strが指す文字列は読み込み専用なので書き換えできない) 注:このサイトで投稿すると「連続した複数の半角空白」は「1つの半角空白」に勝手に直されて投稿されるので40個の空白を並べて書けません。実際にプログラムに書く時は「40個の空白を並べて」書いてください。
- chie65536(@chie65535)
- ベストアンサー率44% (8766/19892)
>これは何がだめですか? strにn文字分の' 'を埋め込むまでは良いが、そのstrに終端文字の'\0'を付けてないのがダメ。 厳しい事を言えば、fscanfを使うのもダメだし、memsetを使うのもダメだし、mallocを使うのもダメだし、mallocがエラーを返してくるのをチェックしてないのもダメだし、ちゃんとfreeしているか明記してないのもダメ。 で、どうして失敗しているのかと言うと、例えば 1行目が30文字 2行目が20文字 3行目が35文字 だったとしよう。 1行目は、nが40-30で10、+1して11文字のバッファがmallocで返される。 そこに10文字の' 'を埋める。 運良く、mallocが返したメモリは「最初は全部ゼロで埋まってる」ので、strには「10文字の空白+終端文字を意味する運良く最初からあったゼロ」が出来る。 それを「30文字のbufにstrcatする」ので「30文字+10文字」になる。 そして、使い終わったstrはfree(str);で開放される(けど、そのメモリには10文字の空白が残ってる) 一見、1行目は正しく動く。 2行目は、nが40-20で20、+1して21文字のバッファがmallocで返される。 そこに20文字の' 'を埋める。 運良く、mallocが返したメモリは、さっき使ったメモリと同じ場所なので「10文字の空白と、11文字目以降全部ゼロで埋まってる」ので、strには「20文字の空白+終端文字を意味する運良く最初からあったゼロ」が出来る。 それを「20文字のbufにstrcatする」ので「20文字+20文字」になる。 そして、使い終わったstrはfree(str);で開放される(けど、そのメモリには20文字の空白が残ってる) 一見、2行目も正しく動く。 3行目は、nが40-35で5、+1して6文字のバッファがmallocで返される。 そこに5文字の' 'を埋める。が、そこには「前回mallocして、使用後にfreeした、使い終わったメモリの残骸」があるので、その残骸には「既に20文字の空白が埋まっている」ので、5文字の' 'を埋めても意味は無い。 残念ながら、mallocが返したメモリは、さっき使ったメモリと同じ場所なので「20文字の空白と、21文字目以降全部ゼロで埋まってる」ので、strには「20文字の空白+終端文字を意味する運良く最初からあったゼロ」が出来る。空白を5文字しか埋めてないのに。 それを「35文字のbufにstrcatする」ので「35文字+20文字」になる。5文字の空白を足すつもりで、終端文字を付け忘れた所為で、20文字の空白が足されちゃう訳だ。 そして、使い終わったstrはfree(str);で開放される(けど、そのメモリには20文字の空白が残ってる) そして、質問者さんは >全部40という長さになっていない、40超えるものも多数出ました。 って悩む事になる。 とりあえずの修正なら memset(str, ' ', n); の直後に str[n] = '\0'; ってのを追加すれば大丈夫。 で、mallocとかstrlenとかmemsetとか使わず、最も簡単に済ます方法は、以下の通り。 char buf[260] /* scanfを行うのに充分な大きさを確保しておく。もちろん、buf[41]ではダメ */ ・・・・・(省略) buf[40] = '\0'; /* 元々のbufが40文字以上だった時に41文字目以降を捨てる。これを忘れると41文字以上の行が出来てしまう */ fprintf(fpt,40,"%-40s\n",buf); /* 左揃えで40文字の幅に書き込めば終わり */ で、本当の所を言うと「buf[40] = '\0';の処理で、行末で40バイトに切り捨てる際に、漢字などの2バイト文字の後半バイトだけ切り捨ててしまう」と言う事が起きないように「レコードの末尾に2バイト文字の先頭バイトだけ残る場合は、それを空白に置き換える」と言う処理も必要だったりする。 以下蛇足。 冒頭の「fscanfを使うのもダメ」な理由。 もし「入力ファイル名の指定を間違えて、読みこんだファイルがテキストファイルじゃなく、空白文字や改行コードが出てこないバイナリファイルだった」としたら、scanfは何文字のデータをbufに入れようとするだろう? 「うっかり、5メガバイトある動画ファイル」を入力に指定したら?そして、その5メガバイトの中に「空白文字や改行コードが1つも無い」としたら? scanfは、言われた通り、bufに「空白文字や改行コードが現れるまで読みこもう」とするだろう。 そして「bufのアドレスに5メガバイトのデータを読み込もう」として、プログラムが落ちてしまう。メモリをグチャグチャに壊したままで。 なので「絶対の保障」が無い限り、scanfやfscanfは使っちゃいけない。
- titokani
- ベストアンサー率19% (341/1726)
単に、 fprintf(fpt,"%-40s\n",buf); でいいんじゃないかと。
- ssk38
- ベストアンサー率44% (22/49)
#include <stdio.h> #include <string.h> #define BUFSIZE 10000 #define WIDE 40 int main(int argc, char *argv[]) { FILE *fp; char buf[BUFSIZE]; int n; if (argc == 1) { fprintf(stderr, "USAGE: %s <input>\n", argv[0]); return 1; } if (NULL == (fp = fopen(argv[1], "r"))) { perror("fopen error"); return 1; } while (NULL != fgets(buf, BUFSIZE, fp)) { while (buf[strlen(buf)-1] == '\n' || buf[strlen(buf)-1] == '\r') { buf[strlen(buf)-1] = '\0'; } for (n = strlen(buf); n < WIDE; n++) { buf[n] = ' '; } buf[WIDE] = '\n'; buf[WIDE+1] = '\0'; fprintf(stdout, "%s", buf); } return 0; } ポイントは文字列の長さと改行コード'\n'と終端文字'\0'
- Yanch
- ベストアンサー率50% (114/225)
質問の内容も、何をやりたいのかも、何故やりたいのかも、何がわからないのかも、 伝わってきません。 ひとつひとつ整理していってください。 ●目的。 ●理由。 ●わかっている事。 ●わからない事。 ●期待する動作。 ●現状どうなっているのか。 > 可変長から固定長 可変長、固定長と書かれていますが、何を指していますか? 可変長、固定長がどい言うものか理解していますか? > ある可変長のファイルがあって、 これは、何かのデータを格納してあるテキストファイルか何かですか? > レコード長を全て40にそろいたい どう言うファイルレイアウトを想像していますか? ファイル仕様書にはどう定義されていますか? > 実行してみたところ、全部40という長さになっていない、40超えるものも多数出ました。 どのようにして確認しましたか? > fprintfをつかったからだめですか? ファイルの書式によります。 ファイル仕様書にしたがってください。 > それともmalloc freeにする必要ありますか? 判断にあたいするだけの情報が提供されていないようです。 これでは、回答できません。 > まったく検討つかないです。 状況を整理する事からはじめてください。 わかっている事とわかっていない事の切り分けなど進めると、 もう少し有効的な質問が書ける様になるかと思います。 ●以下憶測で作成したサンプルプログラムです。 ---------------------------------------------------------------------- #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { char input_filename[] = "input.txt"; char output_filename[] = "output.bin"; FILE *pInputFile = NULL; FILE *pOutputFile = NULL; char inputBuffer[1024]; char editBuffer[40]; int editBufferLen = 0; /* * ●ファイルを開く */ pInputFile = fopen(input_filename, "r"); if (pInputFile == NULL) { perror("入力ファイルを開けませんでした."); exit(1); } pOutputFile = fopen(output_filename, "wb"); if (pOutputFile == NULL) { perror("出力ファイルを開けませんでした."); fclose(pInputFile); exit(1); } /* * ●ファイルの行毎に処理するためのループ処理 * ファイル終端に達するまで、繰り返し処理する。 */ while (fgets(inputBuffer, sizeof(inputBuffer), pInputFile) != NULL) { /* * ●1行毎の処理 */ sscanf(inputBuffer, "%s", editBuffer); /* 行末の改行コードを除去 */ editBufferLen = strlen(editBuffer); memset(editBuffer + editBufferLen, ' ', sizeof(editBuffer) - editBufferLen); fwrite(editBuffer, 40, 1, pOutputFile); } /* * ●ファイルを閉じる */ fclose(pInputFile); fclose(pOutputFile); return 0; } ----------------------------------------------------------------------
- tsuduki999
- ベストアンサー率35% (6/17)
あぁ。それ以前にlenの中身は変なのが入っているんじゃ。。。 それと、ついでなので、 コーディングスタイルとして、n のサイズはチェックした方がよいですね。
- tsuduki999
- ベストアンサー率35% (6/17)
単純に、改行コードを出力していないからじゃないですか? fputs() を利用するか、fprintf(fpt,"%s\n", buf)としてみてください。 あと。strcat()は文字列の連結なので、memcpy()を利用した方がよいと思いますよ。
お礼
ありがとうございます。memcpyで試してみたいと思います。
お礼
ありがとうございました。私がやりたいこと的中に当たりました。早速アドバイス通りにプログラム実行してみたのです。作られたファイルをレコード長の長さを確認するため以下のプログラムを作りました。 while(fgets(buf,REC,fp)!= NULL){ len = strlen(buf); printf("レコード長 :%d\n",len); } 結果は レコード長 :39 レコード長 :2 というふうになったですが、これはどいう意味か、教えて頂ければ嬉しいのですが。。。もしかしたら長さ2のところは改行と終端文字の値でしょうか? ちなみに、C言語を詳しくなるため、お勧めの本があれば教えて頂きたいです。よろしくお願いします