C语言读取文件并存储为结构体数组的问题求助
问题描述
我有一个存储学生数据的文件,格式如下:
ID avg_scores first_name last_name city department
定义的student结构体如下:
typedef struct student { int student_id; int avg_scores; char* first_name; char* last_name; char* city; char* department; } *student;
我希望逐行读取文件并将数据存储为student结构体数组,但在char*类型字段的处理上遇到问题,想请教遗漏了什么?
另外,为什么必须为这些char*字段单独调用malloc?不这么做就无法正常运行,我对此不太理解,而且我也不知道每个字段的具体长度。
以下是我编写的函数:
student * getStudents(FILE * students, int numberOfStudents){ student * studentsPtr = malloc(numberOfStudents * sizeof(student)); for (int i=0; i<numberOfStudents; i++){ studentsPtr[i] = malloc(sizeof(student)); studentsPtr[i]->first_name = malloc(20*sizeof(char)); studentsPtr[i]->last_name = malloc(20*sizeof(char)); studentsPtr[i]->city = malloc(20*sizeof(char)); studentsPtr[i]->department = malloc(20*sizeof(char)); fscanf(students, "%d %d %s %s %s %s", &studentsPtr[i]->student_id, &studentsPtr[i]->avg_scores, studentsPtr[i]->first_name, studentsPtr[i]->last_name, studentsPtr[i]->city, studentsPtr[i]->department); } return studentsPtr; }
问题解答
一、你的代码里遗漏的问题
- 结构体语法错误:
int avg_scores后面少了分号,这会直接导致编译失败。 - 结构体内存分配错误:
studentsPtr[i] = malloc(sizeof(student));里的student是指针类型,sizeof(student)得到的是指针的大小(通常8字节),而非结构体本身的大小。应该改成malloc(sizeof(struct student));,你需要分配整个结构体的内存空间,不是指针的空间。 - 固定长度分配的风险:你给每个字符串字段硬分配20字节,如果文件里的字符串超过19字符(加上终止符
\0),fscanf会写入超出分配的内存,触发程序崩溃、数据损坏等未定义行为。 - 未检查
malloc返回值:任何malloc都可能因内存不足返回NULL,不检查的话后续操作会直接触发空指针错误。
二、为什么必须为char*字段调用malloc?
char*只是一个指针变量,它本身不存储任何字符串内容,只用来保存一块内存的地址。如果不为它分配内存:
- 指针的值是随机的「野指针」,
fscanf往里面写数据相当于往未知的内存区域写入,轻则程序崩溃,重则破坏其他程序的内存数据,属于严重的未定义行为。 - 只有调用
malloc(或者用strdup这类封装好的函数),才能在堆上开辟一块专门的内存空间,让char*指向这块空间,用来存储字符串的实际内容。
三、不知道字段长度的解决办法
如果不确定每个字符串的长度,可以用两种方式处理:
- 临时缓冲区中转:
先定义一个足够大的临时字符数组(比如256字节,覆盖绝大多数场景),用fscanf把字符串读入临时数组,再用strlen获取实际长度,最后分配对应大小的内存(记得加1字节存\0),用strcpy把临时数组的内容复制过去。示例:char temp[256]; fscanf(students, "%s", temp); studentsPtr[i]->first_name = malloc(strlen(temp) + 1); strcpy(studentsPtr[i]->first_name, temp); - 限制
fscanf读取长度:
在格式字符串里指定最大读取长度,比如%19s表示最多读19个字符(留1字节存\0),这样即使原字符串更长,也只会截断到19字符,不会超出你分配的20字节内存。但这种方式会丢失超长字符串的内容,适合能确定字段最大长度的场景。
四、修正后的示例代码
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct student { int student_id; int avg_scores; char* first_name; char* last_name; char* city; char* department; } *student; student * getStudents(FILE * students, int numberOfStudents){ // 分配结构体指针数组的内存 student * studentsPtr = malloc(numberOfStudents * sizeof(student)); if (!studentsPtr) return NULL; for (int i=0; i<numberOfStudents; i++){ // 分配单个结构体的内存,用struct student的大小 studentsPtr[i] = malloc(sizeof(struct student)); if (!studentsPtr[i]) { // 内存分配失败时,释放已分配的资源避免泄漏 for (int j=0; j<i; j++) { free(studentsPtr[j]->first_name); free(studentsPtr[j]->last_name); free(studentsPtr[j]->city); free(studentsPtr[j]->department); free(studentsPtr[j]); } free(studentsPtr); return NULL; } char temp[256]; // 读取ID和平均分,检查读取是否成功 if (fscanf(students, "%d %d", &studentsPtr[i]->student_id, &studentsPtr[i]->avg_scores) != 2) { // 实际开发中要在这里处理读取失败的清理逻辑 break; } // 处理first_name fscanf(students, "%s", temp); studentsPtr[i]->first_name = malloc(strlen(temp) + 1); if (!studentsPtr[i]->first_name) { /* 处理分配失败 */ } strcpy(studentsPtr[i]->first_name, temp); // 处理last_name fscanf(students, "%s", temp); studentsPtr[i]->last_name = malloc(strlen(temp) + 1); if (!studentsPtr[i]->last_name) { /* 处理分配失败 */ } strcpy(studentsPtr[i]->last_name, temp); // 处理city fscanf(students, "%s", temp); studentsPtr[i]->city = malloc(strlen(temp) + 1); if (!studentsPtr[i]->city) { /* 处理分配失败 */ } strcpy(studentsPtr[i]->city, temp); // 处理department fscanf(students, "%s", temp); studentsPtr[i]->department = malloc(strlen(temp) + 1); if (!studentsPtr[i]->department) { /* 处理分配失败 */ } strcpy(studentsPtr[i]->department, temp); } return studentsPtr; }
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

