在解析二进制数据时使用带有attibute的结构
已经看到了各种代码,其中一个将数据读入char
或void
,然后将其转换为struct
。 示例是解析数据具有固定偏移的文件格式。
例:
struct some_format { char magic[4]; uint32_t len; uint16_t foo; }; struct some_format *sf = (struct some_format*) buf;
为了确保这始终有效,需要使用__attribute__((packed))
来对齐struct
。
struct test { uint8_t a; uint8_t b; uint32_t c; uint8_t d[128]; } __attribute__((packed));
在阅读大而复杂的文件格式时,这肯定会使事情变得更加简单。 通常使用具有30多个成员的structs
来阅读媒体格式等。
它也很容易在一个巨大的缓冲区中读取并转换为适当的类型,例如:
struct mother { uint8_t a; uint8_t b; uint32_t offset_child; }; struct child { ... } m = (struct mother*) buf; c = (struct child*) ((uint8_t*)buf + mother->offset_child);
要么:
read_big_buf(buf, 4096); a = (struct a*) buf; b = (struct b*) (buf + sizeof(struct a)); c = (struct c*) (buf + SOME_DEF); ...
将这样的结构快速写入文件也很容易。
我的问题是这种编码方式有多好或多坏。 我正在寻找各种数据结构,并将使用最好的方法来处理这个问题。
- 这是怎么做的 ? (如:这是常见做法。)
-
__attribute__((packed))
总是安全吗? -
使用我在想什么?,谢谢@Amardeepsscanf
更好吗? - 是否更好地创建一个函数,其中一个启动结构与转换和位移。
- 等等
截至目前,我主要在数据信息工具中使用它。 像列出某种类型的所有结构一样,其值为文件格式,例如媒体流。 信息倾销工具。
它有时是如何完成的。 只要您正确使用,打包就是安全的。 使用sscanf()意味着您正在读取文本数据,这是一种与结构中的二进制图像不同的用例。
如果您的代码不需要跨编译器和/或平台(CPU架构)的可移植性,并且您的编译器支持打包结构,那么这是访问序列化数据的完全合法的方式。
但是,如果您尝试在一个平台上生成数据并在另一个平台上使用它,则可能会出现问题,原因如下:
- 主机字节顺序(Little Endian / Big Endian)
- 语言基元类型的不同大小(例如,长度可以是32位或64位)
- 代码更改一方但不改变另一方。
有些库可以简化序列化/反序列化并处理大多数这些问题。 在必须跨越进程和主机的系统上,这种操作的开销更容易合理。 但是,如果您的结构非常复杂,使用ser / des库可能仅仅因为易于维护而合理。
这是怎么做的?
我不明白这个问题。 编辑:你想知道这是否是一个常见的习语。 在可以接受对GNU扩展的依赖的代码库中,是的,这是经常使用的,因为它很方便。
是
__attribute__((packed))
总是安全吗?
对于这个用例,几乎是的,除非它不可用。
使用
sscanf
更好吗?
不。不要使用scanf()
。
是否更好地创建一个函数,其中一个启动结构与转换和位移。
它更便携。 __attribute__((packed))
是一个GNU扩展,并不是所有编译器都支持它(虽然我想知道谁关心GCC和Clang以外的编译器,但理论上,这仍然是一个问题)。
到目前为止,我对C语言标准的抱怨之一就是它们对编译器如何布局结构和位字段以排除可能有用的优化(例如,在具有2个幂整数的系统上,编译器将禁止将八个三位字段打包成三个字节]但不提供程序员指定显式结构布局的任何方法。 我曾经经常使用字节指针来读取结构中的数据,但我现在并不像以前那样喜欢这种技术。 当速度不是很关键时,我现在更喜欢使用一个族函数,它使用任何需要的字节顺序将多字节类型写入多个连续的存储单元[例如void storeI32LE(uint8_t **dest, int32_t dat)
或int32_t readI32LE(uint8_t const **src);
]。 在处理器具有正确的字节顺序且结构成员对齐或处理器支持未对齐访问的情况下,此类代码的效率不如编译器可能写入的效率,但使用此类方法的代码可轻松移植到任何处理器,无论如何它的原生对齐和字节序。