最初の問題は、単語のリスト(つまり文字ポインタ)のスペースしか割り当てられていないが、単語自体にスペースを割り当てていないことです。
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]
に配置する必要があります。私はmalloc
、that'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
バッファーをオーバーランさせ、隣接する要素に書くことが悪くなります。
これは、fscanf
とscanf
:部分読み取りに共通する新しい問題を引き起こします。上記のコードが "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が含まれます。
より高い概念レベルでは、この問題の辞書またはハッシュテーブルのデータ構造を調べることができます。 –
2番目のコードスニペットは、150000ではなく20個のポインターのためのスペースを割り当てます。そして、各ワードの文字にスペースを割り当てません。 – user3386109
"私は2Dへのメモリ割り当てを変更しました" - あなたはしませんでした。これは2D配列ではなく、ギザギザの配列です。ポインタは配列ではありません! – Olaf