2017-01-25 12 views
0

私は約15万語(辞書を表す)のメモ帳ファイルを持っています。私は各単語をスキャンしてコンソールに出力しようとしています。この設定はうまく動作します:C - 配列要素に文字列を保存する

void readDictionary(FILE *ifp, int numWords) { 
    fscanf(ifp, "%d", &numWords); 
    printf("%d\n", numWords); 

    int i; 
    char* words = (char*)malloc(20 * sizeof(char)); 
    for(i = 0; i < numWords; i++) { 
     fscanf(ifp, "%s", words); 
     printf("%s\n", words); 
    } 
} 

ただし、このコードは明らかにループするたびに「単語」を上書きします。私は各単語を特定の配列要素に保存しようとしています。 (私はここの周りに読んで、それは私が行うことになっています何のようですので、私は2Dにメモリ割り当てを変更)私は、次のことをやったが、それは瞬時にクラッシュ:

void readDictionary(FILE *ifp, int numWords) { 
    fscanf(ifp, "%d", &numWords); 
    printf("%d\n", numWords); 

    int i; 
    char** words = (char**)malloc(20 * sizeof(char*)); 
    for(i = 0; i < numWords; i++) { 
     fscanf(ifp, "%s", words[i]); 
     printf("%s\n", words[i]); 
    } 
} 

すべてのヘルプは高く評価されます。私は多くの記事を読んだが、それを理解していない。

+0

より高い概念レベルでは、この問題の辞書またはハッシュテーブルのデータ構造を調べることができます。 –

+2

2番目のコードスニペットは、150000ではなく20個のポインターのためのスペースを割り当てます。そして、各ワードの文字にスペースを割り当てません。 – user3386109

+1

"私は2Dへのメモリ割り当てを変更しました" - あなたはしませんでした。これは2D配列ではなく、ギザギザの配列です。ポインタは配列ではありません! – Olaf

答えて

1

最初の問題は、単語のリスト(つまり文字ポインタ)のスペースしか割り当てられていないが、単語自体にスペースを割り当てていないことです。

char** words = (char**)malloc(20 * sizeof(char*)); 

これは、20文字のポインタのための領域を割り当て、wordsに割り当てます。今すぐwords[i]には文字ポインタのためのスペースがありますしかし、の文字ではありません。 はメモリを初期化しないため、

words[i]にはゴミが含まれています。 fscanfに渡すと、fscanfは、文字を書き込むメモリの場所としてwords[i]のゴミを使用しようとします。プログラム内のメモリが破損するか、it tries to read a memory location is isn't allowed to and crashesになる可能性があります。いずれにせよ、それは良くありません。

文字列にメモリを割り当て、それをfscanfに渡し、最後にその文字列をwords[i]に配置する必要があります。私はmallocthat's generally considered unnecessaryの結果をキャストしませんでした

char** words = malloc(numWords * sizeof(char*)); 
for(i = 0; i < numWords; i++) { 
    char *word = malloc(40 * sizeof(char)); 
    fscanf(ifp, "%39s", word); 
    words[i] = word; 
    printf("%s\n", words[i]); 
} 

注意。

また、numWordsのスペースをリストに割り当てました。あなたのオリジナルでは、20ワード分のスペースしか割り当てられません。それを超えると、割り当てられたメモリを上書きし始め、おそらくクラッシュします。経験則として、一定のメモリ割り当ては避けてください。できるだけ早く動的なメモリ割り当てに慣れましょう。


はまた、私は(あるため、文字列の末尾のヌルバイトのマイナス1)私のバッファのサイズに読み取ることが許可されているどのように多くの文字fscanf制限されていることに注意してください。そうでなければ、単語リストに "Pneumonoultramicroscopilicovolcanoconiosis"(45文字)が含まれていれば、wordバッファーをオーバーランさせ、隣接する要素に書くことが悪くなります。

これは、fscanfscanf:部分読み取りに共通する新しい問題を引き起こします。上記のコードが "Pneumonoultramicroscopilicovolcanoconiosis"に遭遇すると、fscanf(ifp, "%39s", word);は最初の39文字を読み、 "Pneumonoultramicroscopilicovolcanoco"と停止します。 fscanfへの次回の呼び出しでは、「ナイザーシス」と表示されます。あなたはそれらを2つの単語のように保存して印刷します。それはまずいです。

これはワードバッファを大きくすることで解決できますが、ほとんどの場合、多くのメモリが無駄になります。

scanf and fscanf have a whole lot of problems and are best avoided。代わりに、全体の行を読み取って、sscanfで解析することをお勧めします。この場合、解析する必要はありません。文字列だけなので、行を取得するだけで十分です。

fgetsは、通常、行を読み込む方法ですが、行内で読み込む必要があるメモリ量を試してみる必要があります。これを軽減するには、大きなラインバッファを用意して、そこから単語をコピーしてください。

void strip_newline(char* string) { 
    size_t len = strlen(string); 
    if(string[len-1] == '\n') { 
     string[len-1] = '\0'; 
    } 
} 

... 

int i; 

/* The word list */ 
char** words = malloc(numWords * sizeof(char*)); 

/* The line buffer */ 
char *line = malloc(1024 * sizeof(char*)); 

for(i = 0; i < numWords; i++) { 
    /* Read into the line buffer */ 
    fgets(line, 1024, ifp); 

    /* Strip the newline off, fgets() doesn't do that */ 
    strip_newline(line); 

    /* Copy the line into words */ 
    words[i] = strdup(line); 

    printf("%s\n", words[i]); 
} 

strdupは、単語に十分な1024バイトをすべてコピーしません。これにより、必要なメモリのみが使用されます。


ファイルについては、特定の数の行を持つように仮定して、問題のレシピを作成します。 ファイルに特定の行数が含まれていると表示されても、を確認する必要があります。さもなければ、あなたはファイルの終わりを過ぎて読むことを試みるときに奇妙なエラーを得るでしょう。この場合、ファイルがnumWords未満の場合は、ゴミを読み込もうとしてクラッシュする可能性があります。代わりに、行がなくなるまでファイルを読む必要があります。

通常、これはwhileループの戻り値fgetsをチェックすることによって行われます。

int i;  
for(i = 0; fgets(line, 1024, ifp) != NULL; i++) { 
    strip_newline(line); 
    words[i] = strdup(line); 
    printf("%s\n", words[i]); 
} 

これは新しい問題が現れますが、どのように我々はどのように大きなwordsを作るために知っているのですか?あなたはそうしない。これにより、メモリを増やして再割り当てすることができます。この回答は非常に長くなっているので、私はそれをスケッチします。

char **readDictionary(FILE *ifp) { 
    /* Allocate a decent initial size for the list */ 
    size_t list_size = 256; 
    char** words = malloc(list_size * sizeof(char*)); 

    char *line = malloc(1024 * sizeof(char*)); 

    size_t i;  
    for(i = 0; fgets(line, 1024, ifp) != NULL; i++) { 
     strip_newline(line); 

     /* If we're about to overflow the list, double its size */ 
     if(i > list_size - 1) { 
      list_size *= 2; 
      words = realloc(words, list_size * sizeof(char*)); 
     } 

     words[i] = strdup(line); 
    } 

    /* Null terminate the list so readers know when to stop */ 
    words[i] = NULL; 

    return words; 
} 

int main() { 
    FILE *fp = fopen("/usr/share/dict/words", "r"); 
    char **words = readDictionary(fp); 

    for(int i = 0; words[i] != NULL; i++) { 
     printf("%s\n", words[i]); 
    } 
} 

これで、リストはサイズ256から始まり、必要に応じて拡大されます。あまりにも多くのメモリを浪費することなく、倍増はかなり高速になります。私の/ usr/share/dict/wordsには235886行あります。 2 または262144に格納することができます。256は2 であるため、reallocを必要なサイズに拡張するには10回の高価なコールが必要です。

リストを返すように変更しました。リストをすぐに使用しようとすると、リストの作成にあまり効果がないからです。これにより、動的にサイズの変更されたリスト(null termination)を扱う際の別の手法を実証することができます。リストの最後の要素はNULLに設定されているため、リストを読む人はいつ停止するかを知っています。これは、リストで長さを渡すよりも安全で簡単です。たくさんでしたが、それはそれは手動でそれを行うには良いことだC.でファイルを扱うときあなたがする必要があるすべての基本的なものだが、幸いなことに、この種をやって作るそこにライブラリがある


はるかに簡単です。例えば、 Gnome Lib provides a lot of basic functionalityには、 arrays of pointers that automatically grow as neededが含まれます。

+0

これ、ありがとう、有益な情報のおかげで!最後の部分については、私の教授が要求したとおり、これらの作業を手動で行う必要がありますが、言語自体をより快適にすることが前提です。 – Josh

+0

@Joshええ、それはあなた自身で数回するのは良いことです。次に、実際のコードになると、ライブラリを使用します。 – Schwern

3

2番目のバージョンでは、20個のポインタのための領域を割り当てますが、これらのポインタは初期化されず、何も指さないままにしておきます。私はあなたが辞書からそれらのポインタの1つによって指定されたメモリに読み込もうとするときに、その問題がどのように現れるか想像することができます。

あなたが単語のためのスペースを割り当てるために、numwordsポインタ

char** words = malloc(numwords * sizeof(*words)); 

、およびそれらのそれぞれのためのスペースを割り当てたいように見えます。

for(i = 0; i < numWords; i++) { 
    words[i] = malloc(20); // by definition, sizeof(char) == 1 
    // ... 

さらに、は、割り当て障害が発生した場合にNULLされる、malloc()の戻り値をチェック行います。

+0

ありがとうございました!私はメモリ割り当て部分で失われましたが、これはあなたがそれを説明したように意味があります。 – Josh

関連する問題