C 使用磁盘上的数据结构时,最好的方法是什么

C 使用磁盘上的数据结构时,最好的方法是什么,c,struct,on-disk,C,Struct,On Disk,我想知道如何最好地使用磁盘上的数据结构,因为存储布局需要与逻辑设计完全匹配。我发现,当你需要为你的存储设置一个特定的布局时,结构对齐和打包并没有多大帮助 我解决这个问题的方法是使用处理器指令定义结构的(宽度),并使用分配时的宽度字符(字节)数组,在添加遵循逻辑结构模型的数据后,将这些数组写入磁盘 例如: 如果我在磁盘上持久保存foo,“flag”值将出现在数据的最末尾。假设我可以在使用fread读取a&foo类型上的数据时轻松使用foo,然后正常使用struct,而无需进一步修改字节 相反,我更

我想知道如何最好地使用磁盘上的数据结构,因为存储布局需要与逻辑设计完全匹配。我发现,当你需要为你的存储设置一个特定的布局时,结构对齐和打包并没有多大帮助

我解决这个问题的方法是使用处理器指令定义结构的(宽度),并使用分配时的宽度字符(字节)数组,在添加遵循逻辑结构模型的数据后,将这些数组写入磁盘

例如:

如果我在磁盘上持久保存foo,“flag”值将出现在数据的最末尾。假设我可以在使用fread读取a&foo类型上的数据时轻松使用foo,然后正常使用struct,而无需进一步修改字节

相反,我更喜欢这样做

#define foo_width sizeof(uint64_t)+sizeof(uint8_t)

uint8_t *foo = calloc(1, foo_width);

foo[0] = flag_value;
memcpy(foo+1, encode_int64(some_value), sizeof(uint64_t));
然后,我只使用fwrite和fread提交和读取字节,但随后将它们解包,以便使用存储在各种逻辑字段中的数据

考虑到我希望磁盘存储的布局与逻辑布局相匹配,我想知道哪种方法最好。。。这只是一个例子

如果有人知道每种方法在解码/解包字节与直接从磁盘表示复制结构方面的效率,请分享,我个人更喜欢使用第二种方法,因为它可以让我完全控制存储布局,但我不准备牺牲性能,因为这种方法需要大量循环逻辑才能将字节解包/遍历到数据中的各个边界


谢谢。

根据您的需求(考虑外观和性能),第一种方法更好,因为编译器将为您完成繁重的工作。换句话说,如果一个工具(在本例中是编译器)为您提供了某些功能,那么您不想自己实现它,因为在大多数情况下,工具的实现会比您的更高效。

我更喜欢接近第二种方法,但没有memcpy:

void store_i64le(void *dest, uint64_t value)
{  // Generic version which will work with any platform
  uint8_t *d = dest;
  d[0] = (uint8_t)(value);
  d[1] = (uint8_t)(value >> 8);
  d[2] = (uint8_t)(value >> 16);
  d[3] = (uint8_t)(value >> 24);
  d[4] = (uint8_t)(value >> 32);
  d[5] = (uint8_t)(value >> 40);
  d[6] = (uint8_t)(value >> 48);
  d[7] = (uint8_t)(value >> 56);
}

store_i64le(foo+1, some_value);
在典型的ARM上,上述store_i64le方法将转换为大约30个字节——这是时间、空间和复杂性的合理折衷。从速度的角度看不是很理想,但在Cortex-M0之类不支持未对齐写入的设备上,也不会比从空间角度看的理想差太多。请注意,编写的代码对机器字节顺序的依赖性为零。如果你知道你正在使用一个小小的endian平台,其硬件将把未对齐的32位访问转换为8位和16位访问序列,你可以将该方法重写为

void store_i64le(void *dest, uint64_t value)
{  // For an x86 or little-endian ARM which can handle unaligned 32-bit loads and stores
  uint32_t *d = dest;
  d[0] = (uint32_t)(value);
  d[1] = (uint32_t)(value >> 32);
}

在工作的平台上速度会更快。请注意,调用该方法的方式与每次字节版本相同;调用方不必担心使用哪种方法。

如果您在Linux或Windows上,那么只需将文件映射到内存,并将指针强制转换为C结构的类型即可。无论您在该映射区域中写入什么,都将以操作系统可用的最有效方式自动刷新到磁盘。这将比打电话“写”更有效率,而且对你来说麻烦最小

正如其他人所提到的,它不是很便携。为了在little-endian和big-endian之间可移植,常见的策略是用big-endian或little-endian编写整个文件,并在访问时进行转换。然而,这会影响你的速度。保持速度的一种方法是编写一个外部实用程序,将整个文件转换一次,然后在将结构从一个平台移动到另一个平台时运行该实用程序


如果您有两个不同的平台通过共享的网络路径访问单个文件,那么如果您仅仅因为同步问题而尝试自己编写文件,您会遇到很多麻烦,因此我建议使用一种完全不同的方法,如使用sqlite。

什么是
解码\u int64
呢?您是否对二进制数据使用字符串函数?如果是这样的话,想想如果二进制值中的一个字节为零会发生什么,为什么不直接写/读结构呢?然后它甚至可以使用填充和正确对齐(除非您计划在不同平台之间移动数据,或者在使用不同编译器的程序之间移动数据,否则最好使用基于文本的序列化数据格式)。我将其更改为encode_int64,抱歉,这是一个输入错误,基本上,它是用来将64位整数编码成一个字节数组中的endianess,因为我没有使用struct来自然地完成这项工作。在另一个问题上,我只需要将存储的逻辑布局与磁盘上的物理布局相匹配,结构是有限的,因为结构元素的顺序限制为每种类型表示的位的顺序。在我给出的示例中,在保持打包和对齐的同时,uint64类型不可能出现在unint64类型之前。OT:使用paren进行宏定义:
#定义foo_宽度(sizeof(uint64_t)+sizeof(uint8_t))
或类似的
2*foo_宽度
都有有趣的结果。Rob Pike的文章值得一读。手动将字节转换为C数据类型在概念上优于blitting策略,并且不会比blitting策略昂贵很多。CPU比磁盘或内存快得多。谢谢分享,如果你能分享更多关于这种方法对使用c结构有多合适的信息,我会很高兴。你有没有发现使用这种方法的任何缺陷,或者你可以分享的一些优点。。。性能方面。@DeLorean:编码效率通常取决于人们愿意为特定体系结构优化的程度。这种方法的优点是集中了所有特定于体系结构的方面。如果已知体系结构,则使用C结构是可行的,但可能无法为具有不同需求的体系结构提供实际的迁移路径(例如,小端与大端)
void store_i64le(void *dest, uint64_t value)
{  // For an x86 or little-endian ARM which can handle unaligned 32-bit loads and stores
  uint32_t *d = dest;
  d[0] = (uint32_t)(value);
  d[1] = (uint32_t)(value >> 32);
}