在C语言中将指针写入文件

6

我有一个结构:

typedef struct student {
  char *name;
  char *surname;
  int age;
} Student;

我需要将结构体写入二进制文件中。
这是我的尝试:
Student *s = malloc(sizeof(*s));

我将我的结构体填充数据,然后使用以下方式将结构体写入文件:
fwrite(s, sizeof(*s), 1, fp);

我的文件中没有名字和姓氏。相反,它们有各自的char *指针地址。
我该如何将char *写入文件而不是指针地址?

7个回答

11
你需要取消指针引用并编写结构体的各个部分。(你不应该直接fwrite一个结构体,而是对其各个部分进行编码并将其写入:
Student *s = malloc(sizeof(*s));
s->name = "Jon";
s->surname = "Skeet";
s->age = 34;

// ....

fwrite(s->name, sizeof(char), strlen(s->name) + 1, fp);
fwrite(s->surname, sizeof(char), strlen(s->surname) + 1, fp);

//This one is a bit dangerous, but you get the idea.
fwrite(&(s->age), sizeof(s->age), 1, fp); 

但是我在结构体中有大约10个字段。这是非常冗长的代码。 - Sergey Gavruk
1
然后你编写一个函数来写入所有内容;并且只需调用它,如下所示:int fwriteStudent(Student *s, FILE *fp);,并记得在修改结构定义时更新该函数。 - Williham Totland
@Sergey:您可以将这些操作封装成一个单独的函数,这样它就不必是“非常长的代码”了。 - Paul R
你可能是指&s->age,否则这将更加危险,并且极不可能产生所期望的结果。 - unwind

4

您需要额外工作来编写结构元素所指向的数据,可能只需要逐个元素地编写它。

或者将您的结构更改为以下内容:

typedef struct
{
    char name[MAX_NAME_LEN];
    char surname[MAX_SURNAME_LEN];
    int age;
} Student;

2
@Williham:在某些情况下,固定的字符串长度是可以接受的——对于一个数据集很小的作业问题,使用它们是完全合适的。使用紧凑但更复杂的表示形式是“过早优化”,用你的话来说,这是“非常糟糕的”。 - Paul R
“[作业]”标签出现得比我回答和发表评论要晚很多。无论如何,“[作业]”标签不能为粗糙的代码开脱。 - Williham Totland
1
固定大小的字段,例如人名,是完全可以接受的 - 毕竟,它们肯定有合理的上限(期望一个名字超过500个字符的人类合理吗?)只要代码检测到并正确处理尝试存储过长输入的情况,就是可以的。(您是否知道最喜欢的文件系统允许的文件名长度的最大值?您能猜出底层数据结构是什么样子的吗?) - caf
3
浪费的空间是速度/空间权衡——磁盘空间很便宜,比 CPU 循环更便宜。无法存储某人的全名肯定是一种失败,我绝对赞成采用非常保守的方法来限制上限,但认为不存在合理的上限是愚蠢的。毕竟,输入一个 500 个字符的名称而没有任何错误的机会几乎为零。此外,在用户应用程序中表现不佳与在文件系统中表现不佳一样是一个问题(这就是为什么人们在 Oracle 中使用 VARCHAR2 而不是在任何地方都使用 CLOB 的原因)! - caf
1
也许吧,但你可以直接说“不要使用fwrite,使用SQLite”。如果需要使用fwrite,则使用固定文本字段可能是适当的。您的概括性陈述“固定字符串长度很糟糕”过于教条,而且在许多情况下是错误的。这是一种权衡取舍,这意味着有时它是正确的答案,有时则不是。 - caf
显示剩余2条评论

3

如果您想直接将结构体写入文件,而不是动态分配它,您需要定义姓名和姓氏的大小。

typedef structure student { char name[100]; char surname[100]; int age; } 

否则,您需要逐个在结构中编写每个信息。

3
@Williham Totland - 我已经给出了你为什么要这样做的原因,即如果您想将结构体按原样写入。不要盲目地将其评论为不好。如果您想在一个fwrite操作中编写结构,请问您有更好的想法吗? - SysAdmin
将结构体作为一个fwrite操作进行编写,正如已经提到的那样,这是一个糟糕的想法™。它几乎完全不可移植,并且并不总是产生期望的结果。可溢出的字符串是一种等待发生安全漏洞的情况。 - Williham Totland
3
在C语言中,相对于逐个编写每个结构体字段,这种方法更具可移植性。但是,它存在不可移植的问题,并且并不总是能得到期望的结果。其他问题,比如安全漏洞的出现,只是因为没有验证或者你编写的代码本身就有错误。 - SysAdmin
3
在某些情况下,拥有一个固定大小的结构是完全适当的,例如,如果我们要将15,000个“学生”记录写入文件,则如果它们都是相同大小,我们可以快速地查找第9,347个记录。如果每个记录的大小不同,那么我们必须先读取前面的9,346个结构(或者拥有一个单独的索引结构)。 - caf
如果你有15000个学生记录,你可能想使用一个数据库,比如说SQLite。 - Williham Totland

3
在C语言中,你必须手动序列化结构体(将它们转换为一系列字节)。如果你希望输出的数据可以被另一台机器读取,你需要考虑大小端endianness。以下是一个简单的例子,展示如何对结构体进行序列化(写入文件),但不考虑大小端/不同字长,因此代码不可移植。
size_t length;

length = strlen(s->name) + 1;
fwrite(&length, sizeof(length), 1, fp);
fwrite(s->name, 1, length, fp);

length = strlen(s->surname) + 1;
fwrite(&length, sizeof(length), 1, fp);
fwrite(s->surname, 1, length, fp);

fwrite(&s->age, sizeof(s->age), 1, fp);

并且要反序列化:

size_t length;

fread(&length, sizeof(length), 1, fp);
s->name = malloc(length);
fread(s->name, 1, length, fp);

fread(&length, sizeof(length), 1, fp);
s->surname = malloc(length);
fread(s->surname, 1, length, fp);

fread(&s->age, sizeof(s->age), 1, fp);

在实际应用中,您应该检查输入的有效性,而不是盲目地假设输入是有效和可信的。此外,您需要决定要使用哪种字节顺序来存储您的intsize_t等,并通过使用位移确保您以正确的字节顺序读取/写入它们。
顺便说一下,您可能想看一下tpl,这是一个简单的C二进制序列化库。

0
你需要对 s 进行解引用,并逐个写出结构体的每个元素,才能将内容实际写入文件:
size_t nameCount = strlen(s->name) + 1;
fwrite(s->name, sizeof(char), nameCount, fp);

size_t surnameCount = strlen(s->surname) + 1;
fwrite(s->surname, sizeof(char), surnameCount, fp);

1
这个结构定义无法正常工作 - sizeof(s->name) 将会返回一个 char * 的大小。 - caf
所以现在你需要将其更改为strlen()* sizeof(char)... (好吧,sizeof(char)将是1,只是为了原则。) - rkellerm

0

你需要逐个编写字段,就像其他人所说的那样。一个可能有帮助的方法是在每个条目之前编写字符串长度,例如。

size_t len;
len = strlen(s->name)+1;
fwrite(&len,sizeof(len),1,stream);
fwrite(s->name,sizeof(*(s->name)),len,stream);
...

然后阅读回来就是一个问题了

Student * s = malloc(sizeof(*s));
size_t len;
fread(&len,sizeof(len),1,stream);
s->name = malloc(len);
fread(s->name,sizeof(*(s->name)),len,stream);
...

0
请注意,无论如何,您都无法在“普通”表单中查看字段,因为您将其写入二进制文件。尝试在fopen的模式参数中不使用“b”打开文件(而是r+、w、a等而不是rb+、wb、ab)。
当使用fread时(从二进制文件中,在以"rb"模式打开文件后),您应该按预期获得结构字段。

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