我正在尝试从C中的文本文件中读取CSV。文本文件格式为
1,Bob,bob@gmail.com
2,Daniel,daniel@gmail.com
3,John,john@gmail.com
当我运行程序时,数字显示正常,但名称和电子邮件显示为垃圾。这是我的计划......
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int number;
char* name;
char* email;
} Owner;
Owner owners[100];
int load(char* filename)
{
char buffer[200];
char token[50];
Owner* owner;
int owners_size = 0;
FILE* file = fopen(filename, "r");
while(fgets(buffer, 200, file) != NULL)
{
owner = (Owner*)malloc(sizeof(Owner));
owner->number = atoi(strtok(buffer, ","));
owner->name = strtok(NULL, ",");
owner->email = strtok(NULL, ",");
owners[owners_size++] = *owner;
}
fclose(file);
return owners_size;
}
int main()
{
int choise, owners_size, index;
char* owners_filename = "owners2.txt";
owners_size = load(owners_filename);
if(owners_size)
{
printf("owners size: %d\n\n", owners_size);
for(index = 0; index < owners_size; index++)
printf("%d, %s %s\n", owners[index].number, owners[index].name, owners[index].email);
}
}
谁能告诉我原因是什么。感谢您的帮助。
答案 0 :(得分:5)
两个问题:
您没有为结构中的字符串分配空间:
typedef struct
{
int number;
char *name;
char *email;
} Owner;
您需要为指向这些指针的指针提供空间以容纳名称。
您继续提供指向缓冲区的指针,该缓冲区将重复用于每行输入:
while(fgets(buffer, 200, file) != NULL)
{
owner = (Owner*)malloc(sizeof(Owner));
owner->number = atoi(strtok(buffer, ","));
owner->name = strtok(NULL, ",");
owner->email = strtok(NULL, ",");
owners[owners_size++] = *owner;
}
第一行存储为缓冲区中的一些指针。然后下一行覆盖缓冲区并再次切断该行,践踏原始输入。
考虑使用strdup()
:
while (fgets(buffer, 200, file) != NULL)
{
owner = (Owner *)malloc(sizeof(Owner));
owner->number = atoi(strtok(buffer, ","));
owner->name = strdup(strtok(NULL, ","));
owner->email = strdup(strtok(NULL, ","));
owners[owners_size++] = *owner;
}
这是一个稍微有点危险的代码(我不会在生产代码中使用它),因为它没有检查strtok()
是否在预期时找到了令牌(或strdup()
成功了)。同样,我也不会在生产代码中使用strtok()
;我会使用POSIX strtok_r()
或Microsoft strtok_s()
(如果可用)或其他替代技术,可能使用strspn()
和strcspn()
。如果strdup()
不可用,您可以使用相同或不同的名称编写自己的名称:
char *strdup(const char *str)
{
size_t len = strlen(str) + 1;
char *dup = malloc(len);
if (dup != 0)
memmove(dup, str, len); // Or memcpy() - that is safe in this context
return(dup);
}
您可能会注意到您的代码仅适用于简单的CSV文件。如果您遇到这样的行(这是合法的CSV),您就会遇到问题(您的值中带引号,并且由于引用字符串中的逗号而导致错误分割):
1,"Bob ""The King"" King","Bob King, Itinerant Programmer <bob@gmail.com>"
答案 1 :(得分:3)
strtok()
返回的指针指向它正在解析的缓冲区内的地址,在本例中是局部变量buffer
。当load()
返回变量时,它超出范围(即使owners
的所有实例都不是指向同一地址)。您需要复制strtok()
返回的字符串。如果可以,您可以使用strdup()
或使用malloc()
和strcpy()
。
不需要malloc()
Owner
的新实例,因为它们的数组已经存在(代码就是内存泄漏)。
请注意,不存在超出owners
数组范围的保护。如果文件的条目数超过100
,那么循环将超出数组的范围。扩展while
的终止条件以防止这种情况:
while(owners_size < sizeof(owners) / sizeof(owners[0]) &&
fgets(buffer, 200, file) != NULL)
{
}
答案 2 :(得分:1)
您只是将指针存储到本地缓冲区中。当您离开load()
时,此buffer
已消失且无法再访问。
您必须先为name
和email
分配内存,然后才能将其复制到Owner
结构中。
char *tok;
tok = strtok(NULL, ",");
len = strlen(tok);
owner->name = malloc(len + 1);
strcpy(owner->name, tok);
...
[编辑:您需要分配len+1
字节,以便为终止NUL
字符留出空间。的 -Zack] 强>
答案 3 :(得分:1)
你只有一个行缓冲区。 load
中循环的每个循环都会破坏前一循环中的文本。如果这还不够糟糕,那么当load
返回时,缓冲区就会被破坏。
快速解决方法是更改
owner->name = strtok(NULL, ",");
owner->email = strtok(NULL, ",");
到
owner->name = strdup(strtok(NULL, ","));
owner->email = strdup(strtok(NULL, ","));
(如果你没有strdup
,得到一台真正的计算机,写起来非常简单。)
但是,如果我正在审核您的代码,我会使用atoi
代替strtol
来修复固定大小的行缓冲区,固定大小的所有者数组,内存泄漏strtok
而不是strsep
,并且缺少引用处理和解析错误恢复,并指出将每一行分配为一个单元然后将指针保存到其中会更有效。