C语言 - 将指针数据保存/加载到文件中

4

首先,如果这个问题以前已经被问过或者有一个明显的解决方案,而我没有看到,那么请原谅。我找到了一个类似的问题,但我认为我的问题比以前问的更深入一些。

我有一个如下所示的结构:

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

Q: 如何在C(不是C ++)中编写并加载指向内存的指针的内容,而不使用固定长度?

如果我将titlebody写入文件,会不会得到垃圾数据而不是实际存储的信息?我不知道日志条目的titlebody的大小,而且大小可能会因条目而异。

我的研究表明,我需要取消引用指针并单独为结构的每个部分fwrite。但是,我不确定如何在不混淆数据和结构的情况下跟踪数据和结构,尤其是对于较大的文件。此外,如果这些不是我想要存储在文件中的唯一项目(例如,我可能希望稍后包括小图像),我不确定如何为方便起见对文件结构进行排序。

另一个(可能是感知到的)问题是,我已经使用malloc为加载数据时的正文/条目字符串分配了内存,当我再次加载条目时,我将如何知道要分配多少内存给字符串?我是否需要扩展我的结构以包括int body_lenint title_len

非常感谢您提供的指导或建议。

3个回答

1

你说得对,在内存中存储这个结构不是一个好主意,因为一旦指针指向的字符串消失了,就没有办法重新获取它们。从实际角度来看,一种方法是声明有限长度的字符串(如果你知道你的字符串有长度限制):

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

如果您需要使用malloc分配标题和正文,可以创建一个“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。 - Ashalynd
谢谢您的建议,但我已经提到我希望避免固定长度。 - Chortle
1
我所阐述的第二个选项不使用固定长度。 - Ashalynd

1

(我关注的是Linux的观点,但它也可以适用于其他系统)

序列化

你想要实现的通常被称为序列化(引用维基百科)或编组:

序列化是将数据结构或对象状态转换为格式,以便稍后在同一台计算机或另一台计算机上存储和重建

指针I/O

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

聚合数据的序列化

你可以使用一些文本格式,如JSON(我实际上建议这样做),或其他格式,如YAML(或者也许发明自己的格式,例如受s-exprs启发)。习惯使用文本格式(自Unix在1980年之前就有了这个习惯)而不是二进制格式(如XDRASN/1,...)已经成为一种良好的习惯。而且许多协议(HTTP、SMTP、FTP、JSONRPC等)都是文本协议。
请注意,在当前系统上,I/O比计算速度慢得多,因此与网络或磁盘I/O相比,文本编码和解码的相对成本很小(参见Answers 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等...)。

PS. 闭包(或任何包含指向代码的指针)的序列化可能会很具有挑战性。您需要定义确切的含义。

PPS. 一些语言提供了内置的支持序列化和编组。例如,Ocaml有Marshal模块,Python有pickle


0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接