C-将字符串保存到数组元素中



我有一个记事本文件,大约有150000个单词(代表一本字典)。我正在试着扫描每个单词并将其打印到控制台上。此设置运行良好:

void readDictionary(FILE *ifp, int numWords) {
fscanf(ifp, "%d", &numWords);
printf("%dn", numWords);
int i;
char* words = (char*)malloc(20 * sizeof(char));
for(i = 0; i < numWords; i++) {
fscanf(ifp, "%s", words);
printf("%sn", words);
}
}

然而,这段代码显然在每次循环时都会覆盖"单词"。我正在尝试将每个单词保存到某个数组元素中。我做了以下操作,但它立即崩溃(我将内存分配更改为2D,因为我在这里阅读,这似乎是我应该做的):

void readDictionary(FILE *ifp, int numWords) {
fscanf(ifp, "%d", &numWords);
printf("%dn", numWords);
int i;
char** words = (char**)malloc(20 * sizeof(char*));
for(i = 0; i < numWords; i++) {
fscanf(ifp, "%s", words[i]);
printf("%sn", words[i]);
}
}

感谢您的帮助。我读了很多帖子,但还没有弄清楚。

在第二个版本中,您为20个指针分配了空间,但这些指针没有初始化,也没有任何指向。我相信您可以想象,当您试图从字典中读取由其中一个指针指定的内存时,会出现什么问题。

看起来你想为numwords指针分配空间

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

,并为每个单词分配空间。

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

此外,do检查malloc()的返回值,如果分配失败,该值将为NULL

第一个问题是您只为单词列表(即字符指针)分配了空间,但没有为单词本身分配空间。

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

这为20个字符的指针分配空间,并将其分配给words。现在words[i]有空间容纳字符指针,但没有空间容纳字符

words[i]包含垃圾,因为malloc不初始化内存。当您将其传递到fscanf时,fscanf会尝试使用words[i]中的垃圾作为写入字符的内存位置。这可能会损坏程序中的一些内存,或者更有可能会尝试读取不允许读取的内存位置并崩溃。不管怎样,都不好。

您必须为该字符串分配内存,然后将其传递给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("%sn", words[i]);
}

请注意,我没有投射malloc的结果,这通常被认为是不必要的。

还要注意,我在列表中为numWords分配了空间。您的原始文件只为20个单词分配空间,一旦超过这个空间,它就会开始覆盖分配的内存,并可能崩溃。根据经验,避免恒定的内存分配。尽快适应动态内存分配。


还请注意,我将允许读取的字符数fscanf限制为缓冲区的大小(由于字符串末尾的空字节,因此减去1)。否则,如果你的单词列表包含45个字符的"肺炎球菌感染",它会溢出word缓冲区,并开始在相邻的元素上乱写,那就太糟糕了。

这导致了fscanfscanf常见的一个新问题:部分读取。当上面的代码遇到"肺孢子虫病"时,fscanf(ifp, "%39s", word);将读取前39个字符"肺孢子菌病"并停止。对fscanf的下一个调用将显示为"niosis"。你会把它们当作两个单词来存储和打印。这不好。

你可以通过增加单词缓冲区来解决这个问题,但现在大多数单词都会浪费大量内存。

CCD_ 21和CCD_。相反,最好读取整行并使用sscanf进行解析。在这种情况下,您不需要进行任何解析,它们只是字符串,因此获取行就足够了。

fgets是读取一行的常用方法,但这也需要你试着猜测这行需要读取多少内存。为了缓解这种情况,有一个大的行缓冲区,并从中复制单词

void strip_newline( char* string ) {
size_t len = strlen(string);
if( string[len-1] == 'n' ) {
string[len-1] = '';
}
}
...
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("%sn", 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("%sn", 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("%sn", words[i]);
}
}

现在,该列表的大小将从256开始,并根据需要进行增长。加倍增长非常快,不会浪费太多内存。我的/usr/share/dict/words中有235886行。这些行可以存储在218或262144中。256是28,所以它只需要对realloc进行10次昂贵的调用就可以增长到必要的大小。

我把它改成了返回列表,因为如果你只想立即使用它,那么构建列表并没有什么好处。这使我能够演示另一种处理动态大小列表的技术,即null终止。列表中的最后一个元素被设置为NULL,因此任何阅读列表的人都知道何时停止。这比试图通过列表传递长度更安全、更简单。


这太多了,但这是在C中处理文件时需要做的所有基本工作。手动操作很好,但幸运的是,有一些库可以让做这类事情变得容易得多。例如,Gnome-Lib提供了许多基本功能,包括根据需要自动增长的指针数组。

最新更新