如何解决从文件读取数据时出现分段错误
我在用 C 语言解析来自 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;
}
感谢您的帮助!
解决方法
虽然您有一个很好的答案来解决 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。 (例如#define
、12
)。而是在代码顶部声明一个常量,以便在以后需要调整限制时提供方便的单一位置进行更改。
同样,不要硬编码文件名。没有理由为了读取不同的文件而必须重新编译代码。将文件名作为第一个参数传递给您的程序(这就是 16
和 argc
的用途),或者提示用户并将文件名作为输入。上面的代码将文件名作为第一个参数,如果没有提供参数,则默认从 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 { ... }
那些是指导我查看您的代码的要点。 (我确定我忘了再提一两个)如果您有其他问题,请仔细查看并告诉我。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。