我应该如何写一个struct在C文件? [英] How should I write a struct to a file in C?

查看:98
本文介绍了我应该如何写一个struct在C文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图写一个结构二进制文件。我希望我的code是跨平台的,所以我不知道只是一个FWRITE写入整个结构。如果我这样做,那么结构的大小将取决于原始类型为每个平台的尺寸(平台不同,一个int不会有相同的大小作为平台B.所以结构不会相同尺寸或​​,并将该文件最终将是不同的)。

但我对此一无所知,所以我应该写这个结构的每个成员单独,序列化结构 (我怎么能做到这一点?),或只写结构白衣一个FWRITE?
请记住,写入的文件应该是跨平台兼容

在此先感谢

编辑:我的结构是一样的东西。

  typedef结构{
    INT健康;
    浮动的x,y;
    CHAR ID [];
}播放器;


解决方案

既然你说你正在构建一个游戏原型,你主要想使用x86和x86_64 CPU的工作,二进制序列化是比较容易。

但首先,有一些事情要记住:


  • 值在x86和x86_64的不同尺寸。

  • 因此,值也有不同的对齐要求。

  • 双击值可能需要8个或4个字节对齐,这取决于操作系统和架构。

  • 字节顺序通常是一个问题,但新的苹果产品不使用的PowerPC,所以你可以$,你工作小端机P $ ptty太多的保证。

最后一个信息之前,我们开始:您可以收集有关对系统架构在编译的时候,使用编译器宏。在海湾合作委员会, __ __ LP64 意味着你编译一个64位可执行文件。我敢肯定,有对MSVC类似的宏。

应对不同大小的变量问题:

stdint.h 文件包含的typedef为中int8_t int16_t int32_t uint8_t有的int64_t uint16_t uint32_t的uint64_t中。所有这些都保证比特该号码。您可以放心地使用的int64_t 而不是平台之一。如果有没有出于某种原因,这些类型定义的平台,你可以用它们自己的typedef,因为你可以使用编译器preprocessor宏反正获得目标系统的信息定义。

应对不同的对齐问题:

这一个是毛茸茸的。最简单的解决办法是有一个 serializable_player 结构包含的所有字段的球员结构包含,而是告诉编译器它打包(海合会包​​装属性),这样编译器不会把任何填充。然后,写一个文件时,你创建一个 serializable_player 播放,它直接写。

从/到一个序列化的类型可能是架空转换。如果你能负担得起浪费一点内存,你也可以强制调整为结构的每个成员。对齐 8个字节双值可能在这里是个好主意。在GCC,你做到这一点与对齐属性。

请注意:盒装结构通常有性能损失因为你要使用播放在整场比赛中,的的使座无虚席。

应对不同的浮点重新presentations:

如果有,你可能需要支持使用不同的浮点重新presentation的架构的机会,你将无法将它们存储在二进制文件。

我以前在一个游戏开发公司工作,并通过网络发送的浮动点的时候,我们再presented他们作为整数来代替。我们所做的是这样的:计算,我们希望研究的最小分辨率。然后计算最小和最大值也可以是(取决于多人地图玩家们在),最大。在这个重新presentation,我们可以重新present (最大值 - 最小值)/ R 不同的数字,我们需要 LOG2((最大 - 分)/ R)位来存储它。由于接收器也知道最小值,最大值,R,我们并不需要在网络数据包的信息。我们说,重新presented为全0,最大重新psented为全1 $ P $和其他的值是在两者之间。这是一个实时动作游戏和地图不会太大(多人支持64名玩家),我们不得不用甚至小于32位没有问题,队员们不晃动/闪烁,表现还是不错的。

如果您使用类似的方法,你不会有任何麻烦序列化/二进制反序列化的浮动点。

最后,如果你在未来的目标大端架构中,只有小的步骤(如果你不使用union)是必要的。你只需要拥有 convert_to_le _ * 功能为每个尺寸。这些功能应该是空的小端机器,做大端机按位运算。每个序列的每个反序列化之前和之后,你应该调用这些功能,将重新在大端机器不同psented $ P $每个成员。有利于小端的机器可能是一个更好的主意在这里,因为你的主要受众将可能有x86和x86_64的机器。

如果你使用的工会,你的结构重新presentation也应不同的体系结构而异。

我知道,在JSON或明文存储的值更容易,在硬盘空间的优化几乎总是不必要的。但是,如果你打算做一个多人游戏,$ P $二进制序列化/反序列化pparing事先可能是有益的,因为你不会奢侈到实时发送JSON数据。

编辑:正如评论所说,使用JSON看来,如果你想改变你的文件结构,添加更多的字段等,如果有机会做到这一点,你仍然决定使用二进制文件结构,你的文件保存较好应包含前几个字节一个神奇的数字,再presenting版本。当你决定要添加/删除字段,你应该更新的版本。当你从保存文件读取,应先检查版本,并相应地处理每个版本。

编辑2:这个答案的某些部分专用于x86和x86_64的CPU。例如,使用而不是中的int64_t 有道理的,因为在这两种架构长为8字节。如果更广阔的领域需要得到支持,我只想建议为int * _t类型定义。这里有一个例子是Linux内核,其中 S32 用于的签署32位整数的如果需要的大小执行。

I'm trying to write a struct to a binary file. I want my code to be cross platform, so I'm not sure about just writing the whole struct with a fwrite. If I did this, then the size of the struct will vary depending on the size of the primitive types for each platform(in platform A, an int won't have the same size as in platform B. So the struct won't be the same size either, and the file will end up being different).

But I know nothing about this, so should I write each member of the struct individually, serialize the struct (how can I do this?) , or just write the struct whit a fwrite? Remember that the file written should be compatible across platforms

Thanks in advance

EDIT: My struct is something like

typedef struct {
    int health;
    float x, y;
    char ID[];
} Player;

解决方案

Since you stated that you're building a game prototype and you mainly want to work with x86 and x86_64 CPUs, binary serialization is somewhat easier.

But first, there are some things to bear in mind:

  • long values have different size in x86 and x86_64.
  • So, long values also have different alignment requirements.
  • double values may require 8 or 4 byte alignment, depending on OS and architecture.
  • Endianness is usually a problem, but newer Apple products do not use PowerPC, so you can pretty much guarantee that you're working for little-endian machines.

One last info before we begin: You can gather information about about the system architecture in compile time, using compiler macros. In gcc, __LP64__ means that you're compiling an 64 bit executable. I'm sure there are similar macros for msvc.

Tackling different sized variable problem:

stdint.h file contains typedefs for int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t. All of them are guaranteed to be that number of bits. You can safely use int64_t instead of long among platforms. If there is a platform that doesn't have those typedefs for some reason, you can define them using typedefs yourself, since you can get information about the target system using the compiler preprocessor macros anyway.

Tackling different alignment problem:

This one is hairy. The simplest solution would be to have a serializable_player struct containing all the fields that player struct contains, but tell the compiler to pack it (packed attribute for gcc), so that the compiler doesn't put any padding. Then when writing to a file, you create a serializable_player from player and write it directly.

Conversion from/to a serializable type might be an overhead. If you can afford wasting a little memory, you can also enforce alignment to each member of a struct. Aligning double values with 8 bytes may be a good idea here. In gcc, you do that with aligned attribute.

Note: Packed structs usually have a performance penalty and since you're going to use player in the whole game, do not make it packed.

Tackling different floating point representations:

If there is a chance that you may need to support an architecture that uses a different floating point representation, you won't be able to store them in binary.

I used to work in a game development company and when sending floating points over network, we represented them as integers instead. What we did was this: calculate the minimum resolution we want r. Then calculate minimum and maximum value it can be (depending on the multiplayer map players were in), min and max. In this representation, we could represent (max - min) / r different numbers and we needed log2((max - min) / r) bits to store it. Since the receiver also knew about min, max, r, we did not need to include that information in the network packet. We said that min is represented as all 0s, max is represented as all 1s, and the other values are in between. It was a real time action game and the maps weren't too big (multiplayer supported 64 players), we had no trouble with even less than 32 bits, players weren't shaking/flickering, the performance was good.

If you use a similar approach, you will have no trouble serializing/deserializing floating points in binary.

Finally, if you target a big-endian architecture in the future, only small steps (if you're not using union) would be necessary. You only need to have convert_to_le_* functions for each size. Those functions should be empty for little endian machines, do bitwise arithmetic for big endian machines. Before each serialization and after each deserialization, you should call these functions for each member that would be represented differently in big endian machines. Favoring little-endian machines might be a better idea here, since your main audience will probably have x86 and x86_64 machines.

If you're using union, your struct representation should also differ among different architectures.

I know that storing values in JSON or plaintext is easier and space optimization in hard disk is almost always unnecessary. But if you are planning to make a multiplayer game, preparing for binary serialization/deserialization beforehand might be rewarding, since you won't have the luxury to send JSON data in real time.

Edit: As suggested in comments, using JSON seems better if you want to change your file structure, add more fields etc. If there is a chance to do so and you still decide to use a binary file structure, your save files should contain a magic number at the first few bytes, representing version. When you decide to add/remove a field, you should update the version. When you're reading from a save file, you should check version first, and handle each version accordingly.

Edit 2: Some parts of this answer are specific to x86 and x86_64 CPUs. For example, using long instead of int64_t makes sense since long is 8 bytes in both architectures. If a wider area needed to be supported, I would only recommend int*_t typedefs. An example here is linux kernel, in which s32 is used for signed 32 bit integer if a size enforcement is required.

这篇关于我应该如何写一个struct在C文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆