从C中的文本文件中读取CSV

时间:2012-10-22 21:13:45

标签: c

我正在尝试从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);
    }
}

谁能告诉我原因是什么。感谢您的帮助。

4 个答案:

答案 0 :(得分:5)

两个问题:

  1. 您没有为结构中的字符串分配空间:

    typedef struct
    {
        int   number;
        char *name;
        char *email;
    } Owner;
    

    您需要为指向这些指针的指针提供空间以容纳名称。

  2. 您继续提供指向缓冲区的指针,该缓冲区将重复用于每行输入:

    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;
    }
    

    第一行存储为缓冲区中的一些指针。然后下一行覆盖缓冲区并再次切断该行,践踏原始输入。

  3. 考虑使用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已消失且无法再访问。

您必须先为nameemail分配内存,然后才能将其复制到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,并且缺少引用处理和解析错误恢复,并指出将每一行分配为一个单元然后将指针保存到其中会更有效。