我有一个约150,000字的记事本文件(代表字典)。我正在尝试扫描每个单词并将其打印到控制台。此设置工作正常:
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 :(得分:3)
在你的第二个版本中,你为20个指针分配空间,但你保留这些指针未初始化,没有任何指向。我确定你可以想象当你尝试从字典中读取其中一个指针所指定的内存时,这会产生什么问题。
看起来你想为numwords
指针分配空间
char** words = malloc(numwords * sizeof(*words));
,并为每个人分配一个单词的空间。
for(i = 0; i < numWords; i++) {
words[i] = malloc(20); // by definition, sizeof(char) == 1
// ...
此外,执行检查malloc()
的返回值,如果分配失败,将返回NULL
。
答案 1 :(得分:1)
第一个问题是你只为一个单词列表(即字符指针)分配空间,但你没有为单词本身分配空间。
char** words = (char**)malloc(20 * sizeof(char*));
这为20个字符指针分配空间并将其分配给words
。现在words[i]
有一个空格用于字符指针 但不包含字符 。
words[i]
包含垃圾,因为malloc
没有初始化内存。当您将其传递给fscanf
时,fscanf
会尝试使用words[i]
中的垃圾作为内存位置来写入字符。这要么会破坏程序中的某些内存,要么更可能it tries to read a memory location is isn't allowed to and crashes。无论哪种方式,它都不好。
您必须为字符串分配内存,将其传递给fscanf
,最后将该字符串放入words[i]
。
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]);
}
请注意,我没有投射malloc
,that's generally considered unnecessary的结果。
另请注意,我在列表中为numWords
分配了空间。你的原始版本只分配20个单词的空间,一旦它超过它将开始覆盖分配的内存并可能崩溃。根据经验,避免不断的内存分配。尽可能快地习惯动态内存分配。
另请注意,我限制允许读取多少个字符fscanf
到我的缓冲区大小(由于字符串末尾的空字节而减去一个)。否则,如果您的单词列表包含“Pneumonoultramicroscopicsilicovolcanoconiosis”,45个字符,它将超出word
缓冲区并开始在相邻元素上涂鸦,这将是不好的。
这会导致fscanf
和scanf
常见的新问题:部分读取。当上面的代码遇到“Pneumonoultramicroscopicsilicovolcanoconiosis”fscanf(ifp, "%39s", word);
将读取前39个字符,“Pneumonoultramicroscopicsilicovolcanoco”并停止。对fscanf
的下一次调用将显示为“niosis”。您将存储和打印它们,就像它们是两个单词一样。这不好。
你可以通过使单词缓冲区更大来解决这个问题,但现在大多数单词会浪费大量内存。
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 18 或262144中.256是2 8 所以它只需要10次昂贵的realloc
调用就可以增长到必要的大小。
我已将其更改为返回列表,因为如果您要立即使用它,那么构建列表并不是很好。这允许我演示使用动态大小的列表,null终止的另一种技术。列表中的最后一个元素设置为NULL
,因此读取列表的任何人都知道何时停止。这比尝试使用列表传递长度更安全,更简单。
这是很多,但这是在C中使用文件时需要做的所有基本操作。手动操作很好,但幸运的是有些库可以让这类事情变得更容易。例如,Gnome Lib provides a lot of basic functionality包括arrays of pointers that automatically grow as needed。