重要なお知らせ

「教えて! goo」は2025年9月17日(水)をもちまして、サービスを終了いたします。詳細はこちら>

電子書籍の厳選無料作品が豊富!

C言語で可変長から固定長に変換方法は??
ある可変長のファイルがあって、レコード長を全て40にそろいたいと思って以下のようにプログラム作りました:
・・・・・(省略)
len = strlen(buf); // fscanf使ってファイルから読みみ込んだものbufに格納
n = 40-len;
char * str = (char *)malloc(n+1);
memset(str, ' ', n);
strcat(buf,str); //レコード40バイトになるまで空白を詰めていく
fprintf(fpt,buf);//fprintf使って新しいファイルに書き込み
・・・・・・(省略)
実行してみたところ、全部40という長さになっていない、40超えるものも多数出ました。
これは何がだめですか?fprintfをつかったからだめですか?それともmalloc freeにする必要ありますか?まったく検討つかないです。どうかよろしくお願いします

A 回答 (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);
}
    • good
    • 0

追記な訂正。



>fprintf(fpt,40,"%-40s\n",buf);



fprintf(fpt,"%-40s\n",buf);

の誤り。度々すいません。
    • good
    • 0
この回答へのお礼

ありがとうございました。私がやりたいこと的中に当たりました。早速アドバイス通りにプログラム実行してみたのです。作られたファイルをレコード長の長さを確認するため以下のプログラムを作りました。 while(fgets(buf,REC,fp)!= NULL){
len = strlen(buf);
printf("レコード長 :%d\n",len);
}
結果は
レコード長 :39
レコード長 :2
というふうになったですが、これはどいう意味か、教えて頂ければ嬉しいのですが。。。もしかしたら長さ2のところは改行と終端文字の値でしょうか?
ちなみに、C言語を詳しくなるため、お勧めの本があれば教えて頂きたいです。よろしくお願いします

お礼日時:2009/06/01 15:43

蛇足な追記。



今回のように「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個の空白を並べて」書いてください。
    • good
    • 0

>これは何がだめですか?



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は使っちゃいけない。
    • good
    • 0

単に、


fprintf(fpt,"%-40s\n",buf);
でいいんじゃないかと。
    • good
    • 0

#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'
    • good
    • 0

質問の内容も、何をやりたいのかも、何故やりたいのかも、何がわからないのかも、


伝わってきません。

ひとつひとつ整理していってください。

●目的。

●理由。

●わかっている事。

●わからない事。

●期待する動作。

●現状どうなっているのか。


> 可変長から固定長
可変長、固定長と書かれていますが、何を指していますか?
可変長、固定長がどい言うものか理解していますか?

> ある可変長のファイルがあって、
これは、何かのデータを格納してあるテキストファイルか何かですか?

> レコード長を全て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;
}
----------------------------------------------------------------------
    • good
    • 0

あぁ。

それ以前にlenの中身は変なのが入っているんじゃ。。。

それと、ついでなので、
コーディングスタイルとして、n のサイズはチェックした方がよいですね。
    • good
    • 0

単純に、改行コードを出力していないからじゃないですか?



fputs() を利用するか、fprintf(fpt,"%s\n", buf)としてみてください。

あと。strcat()は文字列の連結なので、memcpy()を利用した方がよいと思いますよ。
    • good
    • 0
この回答へのお礼

ありがとうございます。memcpyで試してみたいと思います。

お礼日時:2009/05/28 23:17

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!