代码之家  ›  专栏  ›  技术社区  ›  Muneer

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

  •  1
  • Muneer  · 技术社区  · 11 年前

    我有一个奇怪的问题。我猜不出为什么会这样。我尝试了各种方法。可能是因为我还是c语言的新手。

    请查看以下代码。

    它有两个参数。 --write --read .

    • 在我的 write() 函数写入文件,然后调用 read() 作用这会将数据写入文件,并按预期正确打印3行值。

    • 在我的 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 回复  |  直到 11 年前
        1
  •  4
  •   Emanuele Paolini    11 年前

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

    要么将整个字符串放入结构中(不是字符指针,而是字符数组),要么将字符串单独写入文件。

        2
  •  3
  •   Basile Starynkevitch    11 年前

    不要调用你的函数 read write (这些名称用于Posix函数)。不要期望能够再次读取由另一个 process 。这是 undefined behavior .

    所以在你的 您正在(假设64位x86系统,例如Linux系统)写入12个字节(4个字节,即。 sizeof(int) +8即。 sizeof(char*) ); 最后8个字节是一些 malloc -ed指针。

    在您的 阅读 您正在读取这12个字节。因此,您正在设置 name 字段指向在执行 。这通常不起作用(例如,因为 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;  
    }
    

    请注意,您需要一个函数来释放 锦葵属植物 -电子版 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解码例程;它给出了一个 大学生 堆中的指针(稍后由调用方使用 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中)决定学生的 名称 是一个 flexible array member ,即声明

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

    你真的希望学生姓名的长度不同。然后,不能简单地将不同长度的记录放在 FILE 。您可以使用一些索引文件库,如 GDBM (每个记录都可以是JSON格式)。你可能想用 Sqlite 或真实的数据库 MariaDb MongoDB .

        3
  •  1
  •   Bathsheba    11 年前

    在里面 read() ,您从未为 name 在您的 Student 结构(您的 write() 功能在这方面表现得更好。)

    当你提到它时 printf 语句,调用 未定义行为 .