我有一个奇怪的问题。我猜不出为什么会这样。我试过各种方式。可能是因为我仍然是c语言的新手。
请查看以下代码。
它有2个参数。 --write
和--read
。
在我的write()
函数中,我写入文件,然后调用read()
函数。这会将数据写入文件并按预期正确打印3行值。
在我的read()
函数中,我读了这个文件。当我单独传递--read
参数时,程序会给出segmentation fault
错误消息。虽然在下面的代码中,如果我将静态字符串值分配给char *name
,则此读取函数将按预期工作。
以下是我为模拟我的问题而创建的完整代码。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct _student {
int id;
char *name;
} Student;
void write();
void read();
int main(int argc, char *argv[])
{
if (argc > 1) {
if (strcmp(argv[1], "--write") == 0) {
write();
read();
}
else if (strcmp(argv[1], "--read") == 0) {
read();
}
}
return 0;
}
void write()
{
printf("Write\n");
FILE *fp;
// write student
Student *std_writer = (Student *) malloc(sizeof(Student));
std_writer->id = 10;
//std_writer->name = "Alice"; // But if i remove the below 4 lines and uncommented this line, everything works as expected.
char *a = "Alice";
std_writer->name = malloc(20);
memset(std_writer->name, '\0', 20);
strncpy(std_writer->name, a, 5);
fp = fopen("Student.file", "wb");
fwrite(std_writer, sizeof(Student), 1, fp);
fwrite(std_writer, sizeof(Student), 1, fp);
fwrite(std_writer, sizeof(Student), 1, fp);
fclose(fp);
free(std_writer);
}
void read()
{
printf("Read\n");
FILE *fp;
// read student
Student *std_reader = (Student *) malloc(sizeof(Student));
fp = fopen("Student.file", "rb");
while(fread(std_reader, sizeof(Student), 1, fp) == 1) {
printf("ID %i \tName : %s\n", std_reader->id, std_reader->name);
}
fclose(fp);
free(std_reader);
}
请帮助我理解并解决这个问题。
修改
好根据我的理解,根据以下答案,我对结构学生进行了如下操作。
typedef struct _student {
int id;
char name[20];
} Student;
这很有效。
有任何意见吗?
答案 0 :(得分:4)
请注意,您没有写入要提交的学生姓名。你只是写指向那个字符串的指针。当然,这不是你想要的。当您读取文件时,您正在读取不再有效的指针。
将整个字符串放在struct中(不是char指针而是char数组),否则你应该将字符串分别写入file。
答案 1 :(得分:3)
请勿调用您的函数read
和write
(这些名称适用于Posix函数)。并且不希望能够再次读取由不同process写的指针。这是undefined behavior。
所以在你的write
中(假设64位x86系统,例如Linux系统)编写12个字节(4即sizeof(int)
+ 8,即sizeof(char*)
);最后8个字节是某些malloc
- ed指针的数值。
在read
中,您正在阅读这12个字节。因此,您要将name
字段设置为数字指针,该字段恰好在已完成write
的过程中有效。这一般不会起作用(例如因为ASLR)。
通常,对指针执行I / O非常糟糕。它只对同一个过程有意义。
您要做的是serialization。出于software engineering原因,我建议使用文本格式进行序列化(例如JSON,也许使用Jansson库。文本格式不那么脆弱,也更容易调试。
假设您将以JSON格式编码学生,如
{ "id":123, "name":"John Doe" }
这是使用Jansson的可能的JSON编码例程:
int encode_student (FILE*fil, const Student*stu) {
json_t* js = json_pack ("{siss}",
"id", stu->id,
"name", stu->name);
int fail = json_dumpf (js, fil, JSON_INDENT(1));
if (!fail) putc('\n', fil);
json_decref (js); // will free the JSON
return fail;
}
请注意,您需要一个功能来释放malloc
- Student
区域,此处为:
void destroy_student(Student*st) {
if (!st) return;
free (st->name);
free (st);
}
你可能也想要宏
#define DESTROY_CLEAR_STUDENT(st) do \
{ destroy_student(st); st = NULL; } while(0)
现在,这是使用Jansson的JSON解码例程;它在堆中给出一个Student
指针(后来被调用者用DESTROY_CLEAR_STUDENT
销毁)。
Student* decode_student(FILE* fil) {
json_error_t jerr;
memset (&jerr, 0, sizeof(jerr));
json_t *js = json_loadf(fil, JSON_DISABLE_EOF_CHECK, &err);
if (!js) {
fprintf(stderr, "failed to decode student: %s\n", err.text);
return NULL;
}
char* namestr=NULL;
int idnum=0;
if (json_unpack(js, "{siss}",
"id", &idnum,
"name", &namestr)) {
fprintf(stderr, "failed to unpack student\n");
return NULL;
};
Student* res = malloc (sizeof(Student));
if (!res) { perror("malloc student"); return NULL; };
char *name = strdup(namestr);
if (!name) { perror("strdup name"); free (res); return NULL; };
memset(res, 9, sizeof(Student));
res->id = id;
res->name = name;
json_decref(js);
return res;
}
您还可以决定以某种二进制格式序列化(我不建议这样做)。然后你应该定义你的序列化格式并坚持下去。很可能你必须对学生ID,姓名的长度,名称进行编码....
你也可以(在C99中)决定学生的name
是flexible array member,即声明
typedef struct _student {
int id;
char name[]; // flexible member array, conventionally \0 terminated
} Student;
你真的希望学生姓名的长度不一样。然后,您不能简单地将不同长度的记录放在简单的FILE
中。您可以使用一些索引文件库,如GDBM(每条记录可以使用JSON)。您可能希望使用Sqlite或真实数据库,例如MariaDb或MongoDB。
答案 2 :(得分:1)
在read()
中,您无法为name
结构中的Student
分配内存。 (在这方面,你的write()
函数表现得更好。)
当您在printf
语句中引用它时,您将调用未定义的行为。