プロが教える店舗&オフィスのセキュリティ対策術

以下のようなソースでカンマ区切りのテキストファイルであるcsvファイルの行数、列数、要素数を取得するプログラムを作っているのですが、終わりのメモリ解放であるdelete []strのところでプログラムが落ちてしまいます。範囲外のメモリにアクセスしてしまっているのかと思いますが、ミスを特定できないので、教えてください。
読み込むcsvファイルには以下のような小数の値が入っています。
0.23960810421811729043, 11.753428210139766463, …(省略) , 3.8736893050771881164

int main( int argc, char **argv ) {
int csvRowNum, csvColNum, csvElemNum;

GetCSVDataNum ( "C:\\Data.csv", csvRowNum, csvColNum, csvElemNum );

return 0;
}

// csvファイルから行数、列数、要素数を取得する
int GetCSVDataNum ( char *csv_name, int &rowNum, int &colNum, int &elemNum ) {
FILE *fp;
fp = fopen( csv_name, "r" );
if( fp == NULL ){
printf( "ファイルオープンエラー\n" );
exit(1);
}

// ファイルサイズの取得
int fsize;
fseek( fp, 0L, SEEK_END );
fsize = ftell( fp );

// ファイルシーク位置を先頭に戻す
fseek( fp, 0L, SEEK_SET );

// ファイルサイズに合わせて文字列領域確保
char *str;
str = new char[fsize];

char buf[10000];
// ファイルから1行ずつテキストデータを読み込み、
// 連結してbufに格納する
str[0] = '\0';
while ( fgets( buf, sizeof(buf), fp ) != NULL ) {
strncat( str, buf, strlen(buf) );
}

int countSep = 0;// '(カンマ)の数のカウント
int cols = 0;// 各行の列数をカウント
int countNL = 0;// 改行の数をカウント

// 要素数、行数、列数の取得
for ( int i = 0; i < (int)strlen(str); i++ ) {
switch( str[i]){
case ',':
countSep++;
cols++;
break;

case '\n':
countNL++;
cols++;
// 各行で列数が異なるときは、最も大きい列数とする
colNum = (colNum > cols ) ? colNum: cols;
cols = 0;
break;

default:
break;

}
}
rowNum = countNL;
elemNum = countSep + countNL;

// strの解放で落ちる
delete[] str;

fclose( fp );
return 0;
}

A 回答 (7件)

> // ファイルサイズに合わせて文字列領域確保


> char *str;
> str = new char[fsize];
本当にその大きさでいいのかなぁ? 文字列なんだから……ねぇ?
    • good
    • 0
この回答へのお礼

ご指摘ありがとうございます。

csvファイルに書かれている文字数に応じて、文字列領域を確保するにはどのようなコードを書けばいいのかわかっていないので、教えていただけると助かります。

お礼日時:2012/03/27 14:44

#1 に加えて, そもそも ftell を使って何をしたいのかが既に疑問.

    • good
    • 0
この回答へのお礼

回答ありがとうございます。

fopenでファイルを開いて、fseek( fp, 0L, SEEK_END );でファイルの終端までポインタを移動して、ftellすることで、ファイルのサイズが取得できると思って書いていましたが、考え方が間違っているのでしょうか?
ファイルサイズの取得はテキストの文字数を取得したいと思って行なっています。

お礼日時:2012/03/27 23:00

http://support.microsoft.com/kb/68337/ja
http://msdn.microsoft.com/ja-jp/library/0ys3hc0b …
とかですかね。>#2

私的には…
>strncat( str, buf, strlen(buf) );
もちょっとばかし違和感が。
# ありといえばあり…なんでしょうけど。

>終わりのメモリ解放であるdelete []strのところでプログラムが落ちてしまいます。範囲外のメモリにアクセスしてしまっているのかと

は、回答ある通り…ですかね。
ftell()が正しくファイルサイズを返している場合は…'\0'の分が足りない。
ファイルサイズが正しくなかった場合は…strncat()した時にオーバーラン。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

ヌル文字の分を忘れていました。
ファイルサイズは正しく返しているようなので、
文字列最後に加わるヌル文字の分+1して、str = new char[fsize+1];とすれば大丈夫でしょうか。
プログラムとしては動くようになったのですが、正しく理解しているか不安なもので。
strcatの部分の違和感というのも、ヌル文字の分を入れていないからでしょうか?以下のようにすればいいのでしょうか?それとも根本的に使い方がいまいちなのでしょうか?
strncat( str, buf, strlen(buf)+1 );

お礼日時:2012/03/27 22:52

#1ですが、その補足に対しての回答です。



テキストモードで開けたファイルに対するftell()で取れるのは(大雑把に言えば)「文字数」ですよね。
文字列を格納するのに必要な大きさは、本当にその文字数だけでよかったのかなぁ?

……というか、#3さんが答え書いてますね。
    • good
    • 0
この回答へのお礼

補足していただきありがとうございます。

お礼日時:2012/03/28 15:22

順番に読んで順番に処理するだけなら、strそのものを使わずに



// 要素数、行数、列数の取得
while((c=fgetc(fp))!=EOF){
switch( c ){
...

で十分な気がします。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
確かに順に読んで、文字数をカウントするだけならfgetcでよかったですね。

そのあと、取得した文字列から次のカンマまたは改行までの文字列分を取ってきてstrtod関数でdoubleに変換するときに文字列がいいのかと思って、そうしてしまっていました。

お礼日時:2012/03/28 15:34

>strcatの部分の違和感というのも、ヌル文字の分を入れていないからでしょうか?以下のようにすればいいのでしょうか?それとも根本的に使い方がいまいちなのでしょうか?


>strncat( str, buf, strlen(buf)+1 );

バッファオーバーランしない限りはstrcat()でいいでしょう?
そして、今回は(仕様として正しい値が取れるかは不明とはいえ)ファイルサイズ+α分のバッファを動的確保しているのですから、
strncat()にする必要もない…かと思うんですが……。

strncat()の使いどころって…
「コピー先(連結先)よりコピー元の文字列長が長い場合に途中で切れてでもバッファオーバーランさせない」って場合かと思いますが。
# まぁ、元々何文字入っているのかわからないと使いにくいですが。
# strncat( str, buf, (sizeof(str) - strlen(str) - 1) ) って感じ…かな??
    • good
    • 0
この回答へのお礼

親切なご指導ありがとうございます。
strcatとstrncatなど各関数の違いを意識していきます。

お礼日時:2012/03/28 15:37

ああ, 確かに「全部覚えておく」必要はないですね>#5.



そして, 「ファイルを全部記憶できる」だけのメモリを確保しているなら fgets を while でまわす必要がない, と.
    • good
    • 0
この回答へのお礼

コメントありがとうございます。
別の方法での実装も考慮できるようにしていきたいと思います。

お礼日時:2012/03/28 15:38

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