c - 从文件读取数据时出现分段错误
问题描述
我在用 C 语言从 CSV 文件解析数据时遇到分段错误错误。我相信在阅读最后一行 <person[i].status> 时会给出错误,就好像我评论代码运行完美的同一行一样。
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;
}
感谢您的帮助!
解决方案
虽然您有一个很好的答案来解决您的问题,但您可能会使用to begin withstrtok()
来过度复杂化您的代码。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
bysscanf()
至少最低限度地验证了整数转换。不是这样,atoi()
它会很高兴地接受atoi ("my cow")
并失败,默默地返回零,而没有任何迹象表明事情出了问题。
请注意,在每次转换为字符串时,您必须提供一个字段宽度修饰符,以将存储的字符数限制为比数组可以容纳的少一个(为'\0'
nul 终止字符节省空间)。否则scanf()
家庭使用"%s"
或"%[..]"
不比使用更安全gets()
。请参阅为什么 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, "%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)
n++; /* increment count on good conversion */
}
如上图所示#define
,不要在代码中使用MagicNumbers。(例如12
,16
)。相反,在您的代码顶部声明一个常量,如果您的限制以后需要调整,它提供了一个方便的单一位置来更改。
同样,不要硬编码文件名。没有理由为了从不同的文件中读取而必须重新编译代码。将文件名作为第一个参数传递给您的程序(这就是它的argc
用途argv
),或者提示用户并将文件名作为输入。上面,代码将文件名作为第一个参数,如果没有提供参数,则默认读取stdin
(就像大多数 Unix 实用程序一样)。
总而言之,你可以做类似的事情:
#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, MAXC, fp)) { /* protect array, read line */
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)
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()
输出任何带有转换的连续输出块。如果您不需要转换,请使用puts()
或者fputs()
如果需要行尾控制)
最后,不要吝啬缓冲区大小。对于一个领域来说16
似乎非常短(仍在推动它)。通过使用字段宽度修饰符,您可以防止由于覆盖数组边界而导致未定义行为(代码将简单地跳过该行),但您应该添加一个条件以在这种情况下输出错误。对于您的示例数据来说已经足够了,但对于一般用途,您可能希望将其调整为更大的值。name
64
else { ... }
16
示例使用/输出
使用名为 的文件中的示例输入dat/person_id-status.txt
,您可以执行以下操作:
$ ./bin/person_id-status dat/person_id-status.txt
person[0] 1 A John Mott D 30 Z
person[1] 2 B Judy Moor S 60 X
person[2] 3 A Kae Blanchett S 42 y
person[3] 4 B Jair Tade S 21 W
那些结构我查看您的代码的要点。(我确定我忘了再提一两个)看看事情,如果你还有其他问题,请告诉我。
推荐阅读
- module - 在 Rust 2018 中使用模块时,如何解决错误“根目录中没有模块”?
- javascript - 未定义 chrome.storage.sync.get?
- python - 更新 numpy 数组中的值而无需繁琐的 for 循环
- node.js - 根据 Modernizr 服务不同的资产
- python - 如何使用 docker 容器运行服务器?
- python - 使用列表设计可扩展的命令行界面
- flutter - 有没有办法在颤振中打开其他应用程序
- typescript - 打字稿:检查类型是否为联合
- tizen-studio - 签署 Tizen 包时密码无效
- javascript - s% 如何引用 Helmet 组件内的 title 属性?