从文件读取数据时出现分段错误

我在用 C 语言解析来自 CSV 文件的数据时遇到分段错误错误。 我相信错误是在阅读最后一行 时给出的,就好像我注释了代码运行完美的同一行。

CSV 文件的内容:

1;A;John Mott;D;30;Z
2;B;Judy Moor;S;60;X
3;A;Kae Blanchett;S;42;y
4;B;Jair Tade;S;21;W
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Person
{
    int id;
    char key;
    char name[16];
    char rel;
    int age;
    char status;
} Person;

int main()
{
    Person person[12];

    FILE *f = fopen("data.csv","r");
    char buffer[256];

    if (f != NULL)
    {
        int i = 0;
        printf("\nFile OK!\n");
        printf("Printing persons:\n");
        while (fgets(buffer,256,f))
        {
            person[i].id = atoi(strtok(buffer,";"));
            person[i].key = strtok(NULL,";")[0];
            strcpy(person[i].name,strtok(NULL,";"));
            person[i].rel = strtok(NULL,";")[0];
            person[i].age = atoi(strtok(buffer,";"));
            person[i].status = strtok(NULL,";")[0]; // error: segmentation fault

            printf("id: %d\n",person[i].id);
            printf("key: %c\n",person[i].key);
            printf("name: %s\n",person[i].name);
            printf("rel: %c\n",person[i].rel);
            printf("age: %d\n",person[i].age);
            printf("status: %c\n",person[i].status);
            i++;
        }
    }
    else
    {
        printf("\nFile BAD!\n");
    }

    return 0;
}

感谢您的帮助!

shifigo 回答:从文件读取数据时出现分段错误

虽然您有一个很好的答案来解决 strtok() 的问题,但您可能因为使用 strtok() 开始而使代码过于复杂。读取带有固定分隔符的分隔文件时,一次将一行读入足够大小的缓冲区,然后使用 sscanf() 将缓冲区分隔为所需的值可以提供简洁(并且在您的情况下)使用 atoi() 一个更强大的)解决方案。

在这种情况下,您可以使用精心设计的格式字符串轻松分隔您的字段。例如,将每一行读入缓冲区(在本例中为 buf),您可以使用以下命令将每一行分成所需的值:

        if (sscanf (buf,"%d;%c;%15[^;];%c;%d;%c",/* convert to person/VALIDATE */
                    &person[n].id,&person[n].key,person[n].name,&person[n].rel,&person[n].age,&person[n].status) == 6)

int 转换为 sscanf() 至少最低限度地验证了整数转换。与 atoi() 不一样,它会很乐意接受 atoi ("my cow") 并在没有任何迹象表明出现问题的情况下默默地返回零。

注意,在每次转换为字符串时,您必须提供一个 field-width 修饰符,以将存储的字符数限制为比数组可以容纳的少一个(为 {{1} } 空终止字符)。否则,使用 '\0'scanf()"%s" 并不比使用 "%[..]" 安全。见Why gets() is so dangerous it should never be used!

gets() 的数组边界的相同保护适用于您的读取循环。只需在下一次读取之前记录成功转换和测试的计数即可,例如

person[]

如上面的 #define NPERSONS 12 /* if you need a constant,#define one (or more) */ #define MAXNAME 16 #define MAXC 1024 ... char buf[MAXC]; /* buffer to hold each line */ size_t n = 0; /* person counter/index */ Person person[NPERSONS] = {{ .id = 0 }}; /* initialize all elements */ /* use filename provided as 1st argument (stdin by default) */ FILE *fp = argc > 1 ? fopen (argv[1],"r") : stdin; ... while (n < NPERSONS && fgets (buf,MAXC,fp)) { /* protect array,read line */ if (sscanf (buf,&person[n].status) == 6) n++; /* increment count on good conversion */ } 所示,不要在您的代码中使用 MagicNumbers。 (例如#define12)。而是在代码顶部声明一个常量,以便在以后需要调整限制时提供方便的单一位置进行更改。

同样,不要硬编码文件名。没有理由为了读取不同的文件而必须重新编译代码。将文件名作为第一个参数传递给您的程序(这就是 16argc 的用途),或者提示用户并将文件名作为输入。上面的代码将文件名作为第一个参数,如果没有提供参数,则默认从 argv 读取(就像大多数 Unix 实用程序一样)。

总而言之,您可以执行以下操作:

stdin

注意:您只需调用一次 #include <stdio.h> #define NPERSONS 12 /* if you need a constant,#define one (or more) */ #define MAXNAME 16 #define MAXC 1024 typedef struct Person { int id; char key; char name[MAXNAME]; char rel; int age; char status; } Person; int main (int argc,char **argv) { char buf[MAXC]; /* buffer to hold each line */ size_t n = 0; /* person counter/index */ Person person[NPERSONS] = {{ .id = 0 }}; /* initialize all elements */ /* use filename provided as 1st argument (stdin by default) */ FILE *fp = argc > 1 ? fopen (argv[1],"r") : stdin; if (!fp) { /* validate file open for reading */ perror ("file open failed"); return 1; } while (n < NPERSONS && fgets (buf,&person[n].status) == 6) n++; /* increment count on good conversion */ } if (fp != stdin) /* close file if not stdin */ fclose (fp); for (size_t i = 0; i < n; i++) /* output results */ printf ("person[%zu] %3d %c %-15s %c %3d %c\n",i,person[i].id,person[i].key,person[i].name,person[i].rel,person[i].age,person[i].status); } 即可输出任何带有转换的连续输出块。如果您不需要转换,请使用 printf() 或 {{ 1}} 如果需要行尾控制)

最后,不要吝啬缓冲区大小puts() 对于 fputs() 字段来说似乎太短了(16 仍在推动它)。通过使用 field-width 修饰符,您可以防止由于覆盖数组边界(代码将简单地跳过该行)而导致的未定义行为,但您应该添加一个 {{ 1}} 条件在这种情况下输出错误。 name 足以满足您的示例数据,但对于一般用途,您需要将其调整为更大的值。

示例使用/输出

使用名为 64 的文件中的示例输入,您可以执行以下操作:

else { ... }

那些是指导我查看您的代码的要点。 (我确定我忘了再提一两个)如果您有其他问题,请仔细查看并告诉我。

本文链接:https://www.f2er.com/547627.html

大家都在问