問題代碼分析
struct Student stu[30];
struct Student *p;
for (p = stu; p < stu + 30; p++) {
fread(&p, sizeof(struct Student), 1, fp); // 錯誤 1: &p 是指針的地址,非結構體地址
}
printf("%s,%s,%.1f,%.1f,%.1f\n", p->num, p->name, p->num[0], p->num[1], p->num[2]); // 錯誤 2: p 已指向 stu[30] 越界,且格式化字符串錯誤
核心錯誤
- `fread(&p, ...)`:`p` 是 `Student*`,`&p` 是 `Student**` (指針的地址),寫入會破壞指針變量本身,導致後續訪問崩潰或亂碼。正確是 `fread(p, ...)` 或 `fread(&stu[i], ...)`。
- 循環結束後 `p` 指向 `stu+30`** (一個過去末端的位置),解引用 `p->num` 即**越界訪問** (UB)。
- 輸出格式錯誤:`p->num` 是 `char[10]` (學號),`p->name` 是 `char[10]` (姓名),成績在 `p->c[0..2]` (float),代碼卻用 `p->num[0..2]` 打印成績。
- 文本文件用二進制讀取:`cj.txt` 為文本格式 (空格/換行分隔),`fread` 按二進制原樣讀取會包含分隔符、換行符,結構體內存佈局與文本完全不匹配。應用 `fscanf` 或 `fgets`+`sscanf` 解析文本。
正確版本:文本解析 + 邊界檢查
#include
#include
struct Student {
char num[10];
char name[10];
float c[3]; // 語文、數學、外語
};
int main() {
FILE *fp = fopen("cj.txt", "r"); // 文本模式
if (!fp) { perror("打開失敗"); return 1; }
struct Student stu[30];
int count = 0;
// 逐行讀取解析,防止緩衝區溢出
while (count < 30 &&
fscanf(fp, "%9s %9s %f %f %f",
stu[count].num,
stu[count].name,
&stu[count].c[0],
&stu[count].c[1],
&stu[count].c[2]) == 5) {
count++;
}
fclose(fp);
printf("實際讀取 %d 位學生:\n", count);
for (int i = 0; i < count; i++) {
// 格式:學號,姓名,語文,數學,外語 (一位小數)
printf("%s,%s,%.1f,%.1f,%.1f\n",
stu[i].num, stu[i].name,
stu[i].c[0], stu[i].c[1], stu[i].c[2]);
}
return 0;
}
關鍵修正點總結
| 錯誤點 | 修正 |
| `fread(&p, ...)` |
`fscanf(fp, "%9s %9s %f %f %f", ...)` 文本解析 |
| 指針 `p` 循環後越界 |
用下標 `i` 遍歷輸出,或循環前保存 `struct Student *start = p` |
| 成績字段訪問錯誤 `p->num[0]` |
正確字段 `stu[i].c[0]` (float 數組) |
| 未檢查文件打開/讀取返回值 |
`if (!fp) ...`、`fscanf(...) == 5` 確保 5 個字段全匹配 |
| 固定 30 次循環不匹配實際行數 |
`while (count < 30 && fscanf(...) == 5)` 自適應行數 |
補充:若文件真為二進制 (write 寫入)
// 寫入端
fwrite(&stu[i], sizeof(struct Student), 1, fp);
// 讀取端 (二進制模式 "rb")
while (count < 30 && fread(&stu[count], sizeof(struct Student), 1, fp) == 1) count++;
```
*注意:二進制跨平台存在字節序、結構體填充、浮點表示差異,生產環境推薦文本 (CSV/JSON) 或 Protocol Buffers 等序列化協議。*