二进制文件读/写错误,包含struct中的char *指针

时间:2014-03-16 07:46:45

标签: c binaryfiles fwrite

我有一个奇怪的问题。我猜不出为什么会这样。我试过各种方式。可能是因为我仍然是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;

这很有效。

有任何意见吗?

3 个答案:

答案 0 :(得分:4)

请注意,您没有写入要提交的学生姓名。你只是写指向那个字符串的指针。当然,这不是你想要的。当您读取文件时,您正在读取不再有效的指针。

将整个字符串放在struct中(不是char指针而是char数组),否则你应该将字符串分别写入file。

答案 1 :(得分:3)

请勿调用您的函数readwrite(这些名称适用于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中)决定学生的nameflexible array member,即声明

typedef struct _student {
   int id;
   char name[]; // flexible member array, conventionally \0 terminated
} Student;

你真的希望学生姓名的长度不一样。然后,您不能简单地将不同长度的记录放在简单的FILE中。您可以使用一些索引文件库,如GDBM(每条记录可以使用JSON)。您可能希望使用Sqlite或真实数据库,例如MariaDbMongoDB

答案 2 :(得分:1)

read()中,您无法为name结构中的Student分配内存。 (在这方面,你的write()函数表现得更好。)

当您在printf语句中引用它时,您将调用未定义的行为