我正在用C编写一个函数,该函数应该从文件中输入有关学生及其成绩的信息。然而,在运行程序时,有时测试显示分析器在fscanf/fscanf处出现错误,即使输出与预期一致。
/*structure for grade*/
struct Grade {
char subject[51];
int grade;
};
/*structure for student*/
struct Student {
char name[21], surname[21];
int no_grades;
struct Grade grades[100];
};
int input_students(struct Student *students, int n) {
int i = 0, no, j, first;
char name[21], surname[21], subject[51];
FILE *database = fopen("input.txt", "r");
if (database == NULL) {
printf("Error while opening input.txt");
return 0;
}
while (/*i < n && */fscanf(database, "%20s %20s %50s %dn", name, surname, subject,
&no) == 4) {
first = 1;
for (j = 0; j < i; j++) {
if (strcmp(students[j].name, name) == 0 &&
strcmp(students[j].surname, surname) == 0) {
first = 0;
if (students[j].no_grades >= 100) { /*limit for number of grades per student is 100*/
break;
}
strcpy(students[j].grades[students[j].no_grades].subject,
predmet);
students[j].grades[students[j].no_grades].grade = no;
students[j].no_grades++;
break;
}
}
if (first && i < n) {
strcpy(students[i].name, name);
strcpy(students[i].surname, surname);
students[i].no_grades = 0;
strcpy(students[i].grades[students[i].no_grades].subject, subject);
students[i].grades[students[i].no_grades].grade = no;
students[i].no_grades++;
i++;
}
}
/*while (fscanf(database, "%20s %20s %50s %dn", name, surname, subject,
&no) == 4) {
for (j = 0; j < i; j++) {
if (strcmp(students[j].name, name) == 0 &&
strcmp(students[j].surname, surname) == 0) {
if (students[j].no_grades >= 100) {
break;
}
strcpy(students[j].grades[students[j].no_grades].subject,
subject);
students[j].grades[students[j].no_grades].grade = no;
students[j].no_grades++;
break;
}
}
}*/
fclose(database);
return i;
}
/update,注释将代码更改为只有一个循环,同样的事情发生/返回值是从文件中读取其信息的学生的数量,而i <n。>
分析器消息指出fscanf行有错误。
==3928== Invalid write of size 1
==3928== at 0x37154582C7: _IO_vfscanf (in /lib64/libc-2.12.so)
==3928== by 0x371546465A: __isoc99_fscanf (in /lib64/libc-2.12.so)
==3928== by 0x400AD5: input_students (main.c:22)
==3928== by 0x401394: main (main.c:174)
==3928== Address 0x7feff2e00 expected vs actual:
==3928== Expected: stack array "name" of size 21 in frame 2 back from here
==3928== Actual: stack array "surname" of size 21 in frame 2 back from here
==3928== Actual: is 32 before Expected
我似乎找不出这事的原因。我已经仔细检查了每个字符串以' '结束,我已经通过调试器运行了程序,它停在应该停的地方,我没有看到它经过并使用未初始化的值,它所取的值如预期的那样。
我已经翻译了函数和变量的名称,因为它最初不是英文的,因此问题不可能是函数的名称与库函数的名称重叠,但我将在这里更改它。
测试代码。这是自动测试
int i, j, no_students;
struct Student students[10];
FILE* database = fopen("input.txt", "w");
fputs("Pero Peric Osnove_racunarstva 8", database);
fputc(10, database);
fputs("Suljo Suljic Osnove_racunarstva 9", database);
fputc(10, database);
fputs("Pero Peric Inzenjerska_matematika_1 6", database);
fclose(database);
no_students=input_students(students, 10);
printf("%dn", no_students);
for (i=0; i<no_students; i++) {
printf("%s %s ", students[i].name, studenti[i].surname);
for (j=0; j<students[i].no_grades; j++)
printf("%s %d ", students[i].grades[j].subject, students[i].grades[j].grade);
printf("n");
}
该测试的输出。
2
Pero Peric Osnove_racunarstva 8 Inzenjerska_matematika_1 6
Suljo Suljic Osnove_racunarstva 9
当测试代码在main.c中运行时,它没有显示任何编译错误,也没有调试器显示分段错误。
input.txt的示例,由autotest创建。
Pero Peric Osnove_racunarstva 8
Suljo Suljic Osnove_racunarstva 9
Pero Peric Inzenjerska_matematika_1 6
这将是关于分析器的信息,请记住,我仍然只是一个学生
==17000== exp-sgcheck, a stack and global array overrun detector
==17000== NOTE: This is an Experimental-Class Valgrind Tool
==17000== Copyright (C) 2003-2012, and GNU GPL'd, by OpenWorks Ltd et al.
==17000== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==17000== Command: outputP9YqwH
好,下面是我认为发生的事情:
no_students=input_students(students, 10);
所以你期望有10个学生,但你只有4个。所以简单的原因是我们将文件指针database
指向行0
(而不是地址0),所以你有这个:
row name surname subject grade_cnt
0 Pero Peric Osnove_racunarstva 8n
1 Suljo Suljic Osnove_racunarstva 9n
3 Pero Peric Inzenjerska_matematika_1 6<END OF FILE>
注意:在第3行末尾可能是文件的末尾。
第一个循环读取第0,1行…3并尝试读取第4行。没有第4行,所以失败了。但可能发生的是,database
是指向第4行时,第一个循环完成跳过EOF和读取超出文件缓冲区的结束。
有两种测试方法:
- 第4行后输入空行
- 在
while()
循环之间关闭和重新打开文件。
跳过文件指针的另一个原因是:
fscanf(database, "%20s %20s %50s %dn", name, surname, subject, &no) == 4)
注意%d
之后的n
如果n
没有了会发生什么?fscanf()
正在查找'n'.
尝试在输入文件的末尾添加空行。
或删除'n'。
fscanf(database, "%20s %20s %50s %d", name, surname, subject, &no) == 4)
另一个原因是数字,%20s
:
fscanf(database, "%s %s %s %d", name, surname, subject, &no) == 4)
如果我说错了,请在评论中告诉我。
由于fscanf
读取name
的20个字符。
scanf
和fscanf
应谨慎使用,最好避免使用。
解决方案非常简单,不用使用名字、姓氏、主题和no这四个变量。声明一个新变量
要简单得多struct Student s;
并使用它作为输入。
不仅解决了fscanf()的问题,而且其余的代码更简单,更容易阅读。
至于它起作用的原因,还需要进一步研究。
这是重新访问的代码
int input_students(struct Student *students, int n) {
int i = 0, no, j, first;
char name[21], surname[21], subject[51];
FILE *database = fopen("input.txt", "r");
if (database == NULL) {
printf("Error while opening input.txt");
return 0;
}
while (fscanf(database, "%20s %20s %50s %dn", s.name, s.surname, s.grades[0].subject,
&s.grades[0].grade) == 4) {
first = 1;
for (j = 0; j < i; j++) {
if (strcmp(students[j].name, name) == 0 &&
strcmp(students[j].surname, surname) == 0) {
first = 0;
if (students[j].no_grades >= 100) { /*limit for number of grades per student is 100*/
break;
}
students[j].grades[students[j].no_grades] = s.grades[0];
students[j].no_grades++;
break;
}
}
if (first && i < n) {
students[i] = s;
i++;
}
fclose(database);
return i;
}