使用fread将文件内容读入结构中

时间:2011-06-01 01:22:15

标签: c linux struct

在“Unix环境中的高级编程”一书中,有一部分(第8.14页,第251页),其中作者向我们展示了“acct”结构的定义(用于存储会计记录信息)。然后,他展示了一个程序,在该程序中,他将会计数据从文件读入结构(其中关键部分是):

fread (&acdata, sizeof(acdata), 1, fp)

我遇到的麻烦是我听说C编译器有时会在内存中重新排列结构元素,以便更好地利用空间(由于对齐问题)。因此,如果此代码只是获取文件的所有内容并将其粘贴到acdata中(并且文件的内容被排列为与结构定义中指定的顺序相匹配),如果某些元素的元素已被移动,那么如果我在代码中引用它们,我可能无法达到我的预期(因为文件中的数据没有像结构在内存中那样重新排列)。

我缺少什么(因为从我得到的东西看起来不太可靠)?

感谢您的帮助(道歉,如果我在程序上做错了 - 这是我第一次发帖)

4 个答案:

答案 0 :(得分:8)

担心!

你担心这个问题是正确的,并注意它。这是一个令人烦恼的问题,并且经常发生在您将源代码传输到另一台机器时,它具有不同的 - 甚至略有不同的 - 架构,并且可能具有不同的操作系统或可能是不同的编译器;在那里编译你的程序;并期望您的结构在fwrite( )fread( )上保持完整。或者,当您向结构中添加1字节变量时,重新编译,并将二进制文件发送给您的所有朋友。出于某种神秘的原因,你的程序不再适用于他们的机器了。

有时候它会起作用(偶然)并且你从未注意到这个问题;有时它不起作用,你把头发拉了几天。

isssue与struct成员的重新排列无关。编译器不这样做。它与优化无关。

问题是 字节对齐 ,下面提到的维基百科文章告诉您如何修复结构,以便它们始终正确对齐。注意字节对齐是 总是一个好主意 。否则您的程序不可移植。而且,更糟糕的是,您在whiz-bang x86-64上仔细编译并且突然分发给所有客户的程序将无法在其32位计算机上运行。

同样重要的是:注意结构成员的长度和对齐方式。

有一个很好的Wikipedia article 解释了细节。这是非常值得一读的。

我会担心编译器特定的编译指示能够完成这项工作,但仅限于该编译器。如果你在代码中添加了一个pragma,那么你的程序就不再是C了。

答案 1 :(得分:3)

如果在不同的编译器或更高版本的编译器上编译代码,或者甚至使用不同的编译时选项,则结构的布局(填充和对齐,但不是顺序)可能会改变。

不会从同一个已编译程序的运行更改 - 这将是一场噩梦: - )

所以,如果同一个程序(或者技术上,任何程序在编译时编译成相同的结构布局)是那个正在读取的程序,这将正常工作。

C99标准的相关部分是:

  

6.2.6.1/1:除本条款规定外,所有类型的陈述均未指明。

     

6.2.6.1/6(该子条款中唯一提到的结构):当一个值存储在结构或联合类型的对象中时,包括在一个成员对象中,对应于任何填充的对象表示的字节bytes取未指定的值。结构或联合对象的值永远不是陷阱表示,即使结构或联合对象的成员的值可能是陷阱表示。

这是该子条款中唯一提到的结构填充。换句话说,它取决于实现,它们甚至不需要记录它(未指定,而不是实现定义, 需要记录)。

  

6.7.2.1/13:...结构对象中可能有未命名的填充,但不是在其开头。

     

6.7.2.1/15:在结构或联合的末尾可能有未命名的填充。


如果您要创建程序的1.1版并且它使用不同的结构布局(新的编译器,不同的编译器选项,#pragma pack等),很快就会发现您遇到问题单元测试(应包括加载以前版本的文件)。

在这种情况下,您可以在1.1程序中包含一些“智能”,它可以识别较早的文件布局并在数据进入时对其进行转换。这就是为什么好的文件格式通常会有一个版本指示符(对于文件布局)作为该文件中的第一项。

。版本,而不是程序版本

例如,我的应用程序中有相当多的应用程序使用应用程序标识符以及文件前面的16位整数来指示它是什么应用程序和版本,程序的文件加载程序部分至少可以处理当前版本和以前版本(通常是每个版本都创建过)。

程序版本和文件布局版本是分开的 - 例如,如果您发布程序的十个版本而不需要更新文件布局,它们可能会漂移。

答案 2 :(得分:2)

结构根据文件在内存中的编写方式写入文件。订购将是相同的。然而,在写入和读取之间混合编译器可能是一个问题。

答案 3 :(得分:2)

您的计划将保持稳定。

你的问题引发了你实际上没有要求的可移植性建议的篝火。您似乎要问的问题是“这个代码模式和我的程序是否稳定?”。答案是 是。 < / p>

您的结构不会被重新排序。 C99特别禁止重新安排结构构件。 1

此外,布局和对齐不依赖于优化级别。如果他们这样做,所有程序都必须完全使用相同的优化级别,以及所有库例程,内核,所有内核接口等构建。

用户还必须永远跟踪上面列出的每个接口的优化级别,这些接口曾作为系统的一部分进行编译。

内存对齐规则实际上是一种hidden ABI。如果不添加非常专业的和定义很少使用的编译器标志,它们就无法更改。他们倾向于在不同的编译器上工作得很好。 (否则,上面确定的系统的每个元素也必须由同一个编译器编译,或者是无用的。支持给定系统的每个编译器都使用完全相同的对齐规则。什么都行不通,否则。)更改对齐策略的编译器标志通常用于构建给定OS的编译器配置。

现在,您的二进制文件布局虽然非常合理,但 有点老了。它有一些缺点。虽然这些都不是显示停止者,一般都不值得重写应用程序,但它们包括:

  • 很难调试二进制文件
  • 他们确实锁定了单个字节顺序和单个对齐策略。在(遗憾的是,越来越不可能)需要移植到新架构的情况下,您最终可能需要使用memcpy(3)解压缩记录。不是世界末日。
  • 他们没有结构化。像YAML,嗯,甚至XML这样的东西都是自解析的,因此在文件中读取变得容易得多,并且可以使用工具完成某些类型的文件操作。更重要的是,文件格式本身变得更加灵活。但是,您在C和C ++中利用自动解析对象的能力是有限的。

据我了解Paxdiablo的请求,他希望我同意存在编译器选项和编译指示,如果使用它们将改变对齐规则。确实如此。显然,这些选项仅用于特定原因。


1. C99 6.7.2.1(13) 在结构对象中,非位字段成员和位域中的单位 驻留的地址会按声明的顺序增加。