【问题标题】:C - Save/Load Pointer Data to FileC - 将指针数据保存/加载到文件
【发布时间】:2015-12-20 15:10:14
【问题描述】:

如果这个问题之前被问过或者有一个我看不到的明显的解决方案,首先道歉。我发现了一个similar 的问题,但是我相信我所问的问题比之前所问的要更进一步。

我的结构如下:

typedef struct {
        int id;
        char *title;
        char *body;
} journal_entry;

问:如何在不使用固定长度的情况下在 C(不是 C++)中编写和加载指向内存的指针的内容?

我是否错误地认为通过将titlebody 写入文件我最终会得到垃圾数据,而不是我存储的信息?我不知道日记条目的titlebody 的大小,并且大小可能因条目而异。

我自己的阅读表明我需要分别取消引用指针和fwrite 结构的每个部分。但是我不确定如何跟踪数据和结构,而不会使事情变得混乱,特别是对于较大的文件。此外,如果这些不是我打算存储在文件中的唯一项目(例如,我可能希望稍后包含小图像,我不确定如何为方便起见对文件结构进行排序。

另一个(可能被察觉的)问题是我在加载数据时使用malloc 为正文/条目的字符串分配内存,当我希望加载时如何知道为字符串分配多少内存再次进入?我是否需要扩展我的结构以包含int body_lenint title_len

我们将非常感谢您的指导或建议。

【问题讨论】:

    标签: c pointers serialization struct


    【解决方案1】:

    (我专注于Linux的观点,但它可以适应其他系统)

    序列化

    您想要实现的目标通常称为serialization(引用维基百科)- 或编组:

    序列化是将数据结构或对象状态转换为可以在同一台或另一台计算机中存储和重构的格式的过程

    指针输入/输出

    原则上可以读写指针,例如%p 转换规范为 fprintf(3)fscanf(3) (你可以直接 writeread 一个指针,这就像在机器级别的一些 intptr_t 整数。但是,给定的地址(例如 @ 987654341@ ...) 在被不同的进程再次读取时可能无效或具有不同的含义(例如因为ASLR)。

    聚合数据的序列化

    您可能会使用一些文本格式,例如 JSON(我实际上建议这样做)或其他格式,例如 YAML(或者可能发明您自己的格式,例如受 s-exprs 的启发)。与二进制格式(如XDRASN/1、...)相比,更喜欢文本格式(Unix 从 1980 年之前就有这种习惯)是一个公认的习惯。并且许多协议(HTTP、SMTP、FTP、JSONRPC ....)都是文本协议

    请注意,在当前系统上,I/O 比计算慢得多,因此文本编码和解码的相对成本相对而言很小。网络或磁盘 IO(参见 答案表here

    一些聚合数据的编码(例如 C 中的 struct)通常是组合的,通过组合基本标量数据(数字、字符串等)的编码,您可以编码一些更高级别的数据输入。

    序列化库

    大多数格式(尤其是 JSON)都有几个免费的软件库来编码/解码它们,例如JanssonJsonCPP等。

    建议:

    使用 JSON 并将您的 journal_entry 格式化为 JSON 对象,例如

    { "id": 1234,
      "title": "Some Title Here",
      "body": "Some body string goes here" }
    

    具体来说,您将使用一些 JSON 库并首先将您的 journal_entry 转换为某种 JSON 类型(反之亦然),然后使用该库对该 JSON 进行编码/解码

    数据库

    您也可以考虑使用数据库方法(例如sqlite 等...)


    附言。闭包(或任何包含指向代码的指针)的序列化可能具有挑战性。您需要明确这意味着什么。

    PPS。一些语言为序列化和编组提供内置支持。比如Ocaml有一个Marshal模块,Python有pickle

    【讨论】:

      【解决方案2】:

      您是正确的,将这个结构存储在内存中不是一个好主意,因为一旦您的指针指向的字符串消失,就无法检索它们。从实际的角度来看,一种方法是声明有限长度的字符串(如果您知道您的字符串有长度限制):

      typedef struct {
              int id;
              char title[MAX_TITLE_LEGNTH];
              char body[MAX_BODY_LENGTH];
      } journal_entry;
      

      如果你需要用malloc分配title和body,你可以有一个“header”元素来存储整个结构的长度。当您将结构写入文件时,您将使用此元素来确定需要读取多少字节。

      即写:

      FILE* fp = fopen(<your-file-name>,"wb");
      size_t size = sizeof(id)+strlen(title)+1+strlen(body)+1;
      fwrite(&size, sizeof(size), 1, fp);
      fwrite(&id, sizeof(id), 1, fp);
      fwrite(title, sizeof(char), strlen(title)+1, fp);
      fwrite(body, sizeof(char), strlen(body)+1, fp);
      fclose(fp);
      

      阅读(不是特别安全的实现,只是给出想法):

      FILE* fp = fopen(<your-file-name>,"rb");
      size_t size;
      int read_bytes = 0;
      struct journal_entry je;
      fread(&size, sizeof(size), 1, fp);
      void* buf = malloc(size);
      fread(buf, size, 1, fp);
      fclose(fp);
      je.id = *((int*)buf);  // might break if you wrote your file on OS with different endingness
      read_bytes += sizeof(je.id)
      je.title = (char*)(buf+read_bytes);
      read_bytes +=  strlen(je.title)+1; 
      je.body = (char*)(buf+read_bytes);
      // other way would be to malloc je.title and je.body and destroy the buf
      

      【讨论】:

      • 哦,对了。 FP太多后我的C有点生锈了。
      • 感谢您的建议,但我曾提到我希望避免使用固定长度。
      • 我概述的第二个选项不使用固定长度。
      【解决方案3】:

      在内存中,您可以将字符串存储为指向数组的指针。但是在磁盘上的文件中,您通常会直接存储数据。一种简单的方法是存储包含大小的uint32_t,然后存储字符串的实际字节。您还可以将空终止字符串存储在文件中,并在读取它们时简单地扫描空终止符。第一种方法更容易在读取时预先分配所需的缓冲区空间,而无需两次传递数据。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-07-07
        • 1970-01-01
        • 2012-09-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多