这是我的代码。我总是犯错误。我认为出现错误的部分是读取int分数[3]的部分。我该如何解决这个问题?
// code
typedef struct {
int no;
char name[TSIZE];
int score[3];
int sum;
double avg;
} Score;
int num;
if (fscanf(file, "%d%*c", &num) != 1){
printf("$ !ERROR: Wrong file format to num.n");
exit(1);
}
*ptr_s_list = (Score *)malloc(sizeof(Score) * num);
if (*ptr_s_list == NULL){
printf("$ !ERROR: Malloc failed.n");
exit(1);
}
for (int s = 0; s < num; ++s){
if (fscanf(file, "%d%*c", &(*ptr_s_list)[*ptr_s_items].no) != 1
|| fscanf(file, "%[^n]%*c", (*ptr_s_list)[*ptr_s_items].name) != 1
|| fscanf(file, "%d %d %d", &(*ptr_s_list)[*ptr_s_items].score[0], &(*ptr_s_list)[*ptr_s_items].score[1], &(*ptr_s_list)[*ptr_s_items].score[2]) != 1
|| fscanf(file, "%d%*c", &(*ptr_s_list)[*ptr_s_items].sum) != 1
|| fscanf(file, "%lf%*c", &(*ptr_s_list)[*ptr_s_items].avg) != 1
){
printf("$ !ERROR: Wrong file format to '%s'.n", filename);
exit(1);
}
(*ptr_s_items)++;
}
// txt
3 // this int num
1 jay 100 90 80 270 90.0
2 key 90 80 65 235 78.3
3 ray 90 45 65 200 66.7
告诉我如何解决。
您有很多问题,尤其是缺乏一个最小完全可复制示例,但您的主要问题是您正在考虑以一种过于复杂和非常脆弱的方式读取和解析文件中的信息。
当您查看输入文件时,例如
cat dat/txtscore.txt
// txt
3 // this int num
1 jay 100 90 80 270 90.0
2 key 90 80 65 235 78.3
3 ray 90 45 65 200 66.7
(末端空行有意)
你不在乎3
告诉你有多少记录——真的。你不在乎评论行或末尾的空行。您所关心的只是保存所需数据的行。为此,您真正关心的是在给定输入例程的情况下成功解析的行。
无论您正在读取什么,一个好的通用方法是使用fgets()
将整行数据读取到缓冲区(临时字符数组)中,然后尝试使用sscanf()
解析该行。如果使用sscanf()
的解析无法读取您想要的数据,则忽略该行并获取下一行,例如
#define TSIZE 64
...
score *readfile (FILE *fp, score **ptr_s_list, size_t *nelem)
{
...
char buf[TSIZE * TSIZE]; /* buffer to hold each line */
...
/* read a line of input at a time into buf */
while (fgets (buf, sizeof buf, fp)) {
score s; /* temporary stuct to fill */
/* parse record using sscanf from buf, ALWAYS use field-width
* modifier when read into an array to protect array bounds.
*/
if (sscanf (buf, "%d %63s %d %d %d %d %lf", &s.no, s.name,
&s.score[0], &s.score[1], &s.score[2],
&s.sum, &s.avg) != 7) {
continue; /* if parse fails, ignore line, get next */
}
...
这大大简化了文件的读取,并使您的代码更加健壮。
处理分配/重新分配
首先,在C中,不需要强制转换malloc
的返回,这是不必要的。请参阅:我是否投射malloc的结果?。当您从文件中读取和解析数据行时,您可以每行重新分配一次,但这有点昂贵(使用mmap支持的malloc时成本较低,但仍然相对较高)。因此,只需保留两个计数器,(1)保持结构的数量used
,(2)第二个保持已分配和可用的结构数量avail
。您只需要在used == avail
时重新分配。
重新分配时,始终使用临时指针realloc()
。为什么?当(不是如果)内存不足并且realloc()
失败时,它将返回NULL
。如果您使用指针重新分配,例如*ptr_s_list = realloc (*ptr_s_list, ...)
,您刚刚用NULL
覆盖了指针地址,从而无法释放内存,从而导致内存泄漏。
当realloc()
失败时,它不会free()
原始块——这意味着您的数据仍然良好。如果用NULL
覆盖指针,则也会丢失数据。教训:"总是重新分配给一个临时指针">。
有了这一点(以及在查看*ptr_s_list
的行与行之间进行读取,并假设这是由于您将ptr_s_list
指针的地址传递给了读取函数),那么在读取时,在成功解析时,您可以简单地检查是否需要重新分配,然后再将您填充的临时结构s
分配给已分配的列表,例如
score *readfile (FILE *fp, score **ptr_s_list, size_t *nelem)
{
size_t used = *nelem, /* element counter */
avail = *nelem; /* allocated elements available */
...
void *tmp = NULL; /* temp pointer to realloc with */
/* read a line of input at a time into buf */
while (fgets (buf, sizeof buf, fp)) {
...
/* test if realloc needed */
if (used == avail) {
if (!avail) { /* if first allocation, set to 1 */
avail = 1;
}
/* always realloc to a temporary pointer, below doubles size */
tmp = realloc (*ptr_s_list, 2 * avail * sizeof **ptr_s_list);
/* validate EVERY allocation */
if (!tmp) { /* handle error */
perror ("realloc-*ptr_s_list-readfile()");
if (!used) { /* if no records, return NULL */
return NULL;
}
*nelem = used; /* update number of elements */
return *ptr_s_list; /* return pointer to 1st stuct */
}
avail *= 2; /* update available stuct count */
*ptr_s_list = tmp; /* assign new block to ptr */
}
(*ptr_s_list)[used++] = s; /* assign temp struct to element */
}
上面,请注意realloc()
是如何用于第一次和所有后续重新分配的。如果您的原始指针是NULL
,那么realloc()
的行为与malloc()
类似,可以简化您的分配方案。还要注意的是,如果分配第一个结构,avail
将设置为1
,从那时起,当需要更多结构时,可用结构的数量将增加一倍。你可以通过任何你喜欢的方法增加你的分配,但翻倍是重新分配频率和分配增长之间的公平平衡——取决于你自己。
上面,通过传递一个指向元素数量的指针并从此重新分配,您还可以多次调用readfile()
,可能会传递不同的文件以供读取,同时在您分配的结构块中累积所有数据。这不是强制性的,但在如何使用readfile()
函数方面提供了灵活性。通过将指针返回到已分配的结构块,如果需要跟踪单独的分数组,它还允许您将不同的读取分配给不同的指针。同样,这不是强制性的,只是提供了如何使用它的灵活性。
最后,也是可选的,在readfile()
函数返回之前,它最后一次调用realloc()
,将分配缩小到所使用的结构的数量。(编译器不能保证遵守减少,但大多数编译器都会这样做)你可以这样做,再次使用临时指针,如:
/* optional - reduce size to used structs */
tmp = realloc (*ptr_s_list, used * sizeof **ptr_s_list);
if (!tmp) { /* handle error - warn */
perror ("realloc-*ptr_s_list-sizetofit-readfile()");
}
else {
*ptr_s_list = tmp; /* assign new block to ptr */
}
添加一个简短的打印功能来输出所有存储的记录,实现从文件中读取和存储所有数据的一种方法是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TSIZE 64
typedef struct {
int no;
char name[TSIZE];
int score[3];
int sum;
double avg;
} score;
/* funtion to read all scores from file pointer, reallocating
* *ptr_s_list as needed, updating the pointer nelem with the
* number of elements read, retuning a pointer to the allocated
* block of score on success, NULL if no records stored.
*/
score *readfile (FILE *fp, score **ptr_s_list, size_t *nelem)
{
size_t used = *nelem, /* element counter */
avail = *nelem; /* allocated elements available */
char buf[TSIZE * TSIZE]; /* buffer to hold each line */
void *tmp = NULL; /* temp pointer to realloc with */
/* read a line of input at a time into buf */
while (fgets (buf, sizeof buf, fp)) {
score s; /* temporary stuct to fill */
/* parse record using sscanf from buf, ALWAYS use field-width
* modifier when read into an array to protect array bounds.
*/
if (sscanf (buf, "%d %63s %d %d %d %d %lf", &s.no, s.name,
&s.score[0], &s.score[1], &s.score[2],
&s.sum, &s.avg) != 7) {
continue; /* if parse fails, ignore line, get next */
}
/* test if realloc needed */
if (used == avail) {
if (!avail) { /* if first allocation, set to 1 */
avail = 1;
}
/* always realloc to a temporary pointer, below doubles size */
tmp = realloc (*ptr_s_list, 2 * avail * sizeof **ptr_s_list);
/* validate EVERY allocation */
if (!tmp) { /* handle error */
perror ("realloc-*ptr_s_list-readfile()");
if (!used) { /* if no records, return NULL */
return NULL;
}
*nelem = used; /* update number of elements */
return *ptr_s_list; /* return pointer to 1st stuct */
}
avail *= 2; /* update available stuct count */
*ptr_s_list = tmp; /* assign new block to ptr */
}
(*ptr_s_list)[used++] = s; /* assign temp struct to element */
}
*nelem = used; /* update element count */
/* optional - reduce size to used structs */
tmp = realloc (*ptr_s_list, used * sizeof **ptr_s_list);
if (!tmp) { /* handle error - warn */
perror ("realloc-*ptr_s_list-sizetofit-readfile()");
}
else {
*ptr_s_list = tmp; /* assign new block to ptr */
}
return *ptr_s_list; /* return pointer to 1st stuct */
}
/* simple print nelem score records */
void prn_records (score *s, size_t nelem)
{
printf ("n%zu records:nn", nelem);
for (size_t i = 0; i < nelem; i++) {
printf ("%3d %-12s %3d %3d %3d %4d %.1fn", s[i].no, s[i].name,
s[i].score[0], s[i].score[1], s[i].score[2],
s[i].sum, s[i].avg);
}
}
int main (int argc, char **argv) {
size_t nelem = 0; /* number of elements in allocated block of score */
score *s = NULL; /* pointer to allocated block of score */
/* 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;
}
/* read all score records from file, validating at least 1 record read */
if (!readfile (fp, &s, &nelem)) {
fputs ("no records read.n", stderr);
return 1;
}
prn_records (s, nelem); /* print all stored score records */
free (s); /* free allocated memory */
if (fp != stdin) { /* close file if not stdin */
fclose (fp);
}
}
(注意:程序希望文件名作为命令行上的第一个参数读取,或者如果没有提供参数,则默认情况下将从stdin
读取)
示例使用/输出
使用如上所示的文件dat/txtscore.txt
中的示例数据,以及在我的./bin
子目录中编译到readtxtscore
的程序,您将得到:
$ ./bin/readtxtscore dat/txtscore.txt
3 records:
1 jay 100 90 80 270 90.0
2 key 90 80 65 235 78.3
3 ray 90 45 65 200 66.7
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有2个责任:(1)始终为内存块保留一个指向起始地址的指针,因此,(2)当不再需要时,它可以被释放。
您必须使用内存错误检查程序来确保您不会试图访问内存或在分配的块的边界之外写入,尝试读取或基于未初始化的值进行条件跳转,最后确认您释放了所有分配的内存。
对于Linux,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行您的程序即可
$ valgrind ./bin/readtxtscore dat/txtscore.txt
==11434== Memcheck, a memory error detector
==11434== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==11434== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==11434== Command: ./bin/readtxtscore dat/txtscore.txt
==11434==
3 records:
1 jay 100 90 80 270 90.0
2 key 90 80 65 235 78.3
3 ray 90 45 65 200 66.7
==11434==
==11434== HEAP SUMMARY:
==11434== in use at exit: 0 bytes in 0 blocks
==11434== total heap usage: 6 allocs, 6 frees, 6,456 bytes allocated
==11434==
==11434== All heap blocks were freed -- no leaks are possible
==11434==
==11434== For lists of detected and suppressed errors, rerun with: -s
==11434== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
请始终确认您已经释放了分配的所有内存,并且没有内存错误。
仔细看看,如果你有问题,请告诉我。
下面是一个解析sample.txt
输入文件的实现(去掉//东西):
3
1 jay 100 90 80 270 90.0
2 key 90 80 65 235 78.3
3 ray 90 45 65 200 66.7
- 添加缺少的标头
- 定义TSIZE;请注意,结构的实现更改为执行
TSIZE+1
,以便在读取字符串时使用最大字段宽度 - 代码应该在此处使用的函数
main()
中 - 使用
size_t
而不是int
作为num,以避免输入-1。直到需要检查它是否为非零,因为malloc(0)
的行为是实现定义的 - 使用单个
fscanf()
读取每一行。有7个字段,@Fe2O3已经指出1的错误返回值检查,其中3个字段正在读取。读取name
字符串时,请使用最大字段宽度 - 考虑使用
fgets()
读取一行,然后使用sscanf()
进行处理。它给你一个自然的同步点,比如说,通过跳过故障线路。它还允许您访问无效数据以生成更好的错误消息
#include <stdio.h>
#include <stdlib.h>
#define TSIZE 42
#define str(s) str2(s)
#define str2(s) #s
typedef struct {
int no;
char name[TSIZE+1];
int score[3];
int sum;
double avg;
} Score;
int main() {
// declarations with well-defined values needed before first goto
FILE *f;
Score *ptr_s_list = NULL;
f = fopen("sample.txt", "r");
if(!f) {
printf("could not open filedn");
goto out;
}
size_t num;
if(fscanf(f, "%zu", &num) != 1 || !num) {
printf("$ !ERROR: Wrong file format to num.n");
goto out;
}
ptr_s_list = malloc(sizeof *ptr_s_list * num);
if(!ptr_s_list) {
printf("malloc failedn");
goto out;
}
for(Score *p = ptr_s_list; p < ptr_s_list + num; p++) {
int rv = fscanf(f, "%d%" str(TSIZE) "s%d%d%d%d%lf",
&p->no,
p->name,
&p->score[0],
&p->score[1],
&p->score[2],
&p->sum,
&p->avg
);
if(rv != 7) {
printf("$ !ERROR: Wrong file format; %d of 7 fields readn", rv);
goto out;
}
// printf("%d %s %d %d %d %d %gn", p->no, p->name, p->score[0], p->score[1], p->score[2], p->sum, p->av g);
}
out:
free(ptr_s_list);
if(f) fclose(f);
}