Little Endian 结构如何存储位域和长字?
How Are Little Endian Structs With Bitfields and Longwords Stored?
所以,我可以理解为0x1234的一个字,当存储为little-endian时,在内存中变成0x3412。我还看到字节 0x12 作为位域 a:4 并且 b:4 将存储为 0x21。但是,如果我有更复杂的东西怎么办?数据如 0x1700581001FFFFFF 具有以下结构排序?我看到数据存储为 0x7180051001FFFFFF,这对我来说意义不大。似乎 'a' 和 'b' 被交换了,但它们仍保留在结构的开头,而 g 与其他看似随机的交换一起保留在末尾。为什么?另外,我留下了 "LONGWORD" 表示,因为它在代码中。我不确定 4 位如何成为长字,但也许这与这种疯狂有关?
LONGWORD a: 4
LONGWORD b: 4
LONGWORD c: 4
LONGWORD d: 12
LONGWORD e: 8
LONGWORD f: 8
LONGWORD g: 24
在 "implementation-defined manner"。根据 6.7.2.1 结构和联合说明符,C Standard 的第 11 段:
An implementation may allocate any addressable storage unit large
enough to hold a bit-field. If enough space remains, a bit-field
that immediately follows another bit-field in a structure shall be
packed into adjacent bits of the same unit. If insufficient space
remains, whether a bit-field that does not fit is put into the next
unit or overlaps adjacent units is implementation-defined. The order
of allocation of bit-fields within a unit (high-order to low-order or
low-order to high-order) is implementation-defined. The alignment of
the addressable storage unit is unspecified.
回答你的问题但是如果我有更复杂的东西怎么办?类似 0x1700581001FFFFFF 的数据具有以下结构排序?
在这种情况下,如果您想要可移植且可靠的代码,正确的答案是不使用位域。事实上,您未能在您的问题中提供足够的信息让任何人提供关于如何将数据放入您描述的位域的答案,这应该告诉您使用位域时出现的问题。
例如,给定您的位域
LONGWORD a: 4
LONGWORD b: 4
LONGWORD c: 4
LONGWORD d: 12
LONGWORD e: 8
LONGWORD f: 8
LONGWORD g: 24
如果假设位字段使用 16 位 int
类型的值,那么这样布置数据将是完全正确的:
16-bit `int` with `c`,`b`,`a` - in that order
16-bit `int` with `d`
16-bit `int` with `f`,`e` - in that order
16-bit `int` with first 16 bits of `g`
16-bit `int` with last 8 bits of `g` - **after** 8 bits of padding.
这甚至还没有进入存储的字节顺序。
诸如此类的问题(以及关于如何 "designate, in order, the meaning of bits to data" 的评论中提出的观点)不可避免地归结为:您要对数据做什么?
如果您要声明一个数据结构以便一些 C 代码可以写入它,而其他 C 代码可以从中读取,您很少关心字节顺序或位域顺序(或填充,或对齐方式,或任何对齐方式)。
它变得棘手——非常棘手——是当你尝试获取该数据结构时,因为你的 C 编译器将它放置在内存中,并将其写入外部世界或从外部世界读取它。当您尝试这样做时,您最终不得不永远担心类型大小、字节顺序、填充、对齐以及分配位域的顺序。
其实要操心的事情太多了,把它们都钉下来太麻烦了,以至于很多人(包括我自己)建议干脆不要尝试定义可以直接读写的数据结构完全没有。
我的记忆是,big-endinan 机器的编译器倾向于以一种方式布置位域中的位,而对于 little-endian 则以另一种方式布置。但我永远记不起哪条路是哪条路。 (即使我认为我记得,你也不应该相信我。)如果出于某种原因你关心,你将不得不做我一直做的事情,即编写一些小测试程序来构造一些二进制文件数据并以十六进制打印出来,并弄清楚它是如何为您今天使用的 machine/compiler 组合完成的。 (当然,您还必须决定如何应对下周您的 machine/compiler 组合可能发生变化的可能性。)
重新阅读文档后,我没有看到以任何顺序打包位域的任何余地。确实有一个指定的顺序,但它取决于实现方式。但它仍然是可以确定的。简而言之,据我所见,它按顺序按 8 位为一组打包位。我们的 Little Endian 编译器(或者可能是某处的某个选项)的不同之处在于,位的串联将第一个定义的位放在下一个定义的位之后(即,使第一个定义的位不如下一个定义的位重要)。例如:
a:3 = 111 (binary)
b:4 = 0000 (binary)
c:9 = 011111111 (binary)
我们的 Little Endian 编译器(或者,也许还有一些其他选项)将从 'a' 中取出 3 位,并通过将 'a' 添加到 'b' 的末尾与 b 连接.我相信,这与 Big Endian 编译器所做的相反,后者会将 'a' 放在 'b' 之前。所以我推测这是执行此操作的字节顺序,但我们的将通过制作 ba 而不是 ab 来获得 0000111 的 7 位。然后它还需要来自 c 的一位来创建一个完整的 8。它需要 'c' 的最低有效位,即 1,并且之前的位再次附加到该新位的末尾。所以我们有 10000111。然后这个字节 0x87 被存储到内存中,并获取另外 8 位。在此示例中,接下来的 8 位是 01111111,因此它在 0x87 之后存储该字节 0x7F。所以在内存中我们现在有 0x877F。另一种方法(可能是 Big Endian)将以 0xE0FF 结束。如果将现在内存中的 0x877F 解释为 Little Endian 中的一个字,则值为 0x7F87 或二进制形式为 0111111110000111。这恰好与上述连接 'cba' 的数据结构完全相反。
所以让我们对我之前提供的数据进行相同的反向排序:
(0x1700581001FFFFFF 应该被解析如下,但我想这可能并不明显,因为我假设它是一个大端结构)
LONGWORD a: 4 = 0x1
LONGWORD b: 4 = 0x7
LONGWORD c: 4 = 0x0
LONGWORD d: 12 = 0x058
LONGWORD e: 8 = 0x10
LONGWORD f: 8 = 0x01
LONGWORD g: 24 = 0xFFFFFF
使用我们的 Little Endian 配置,通过按 gfedcba 顺序连接,这可以解释为一个值为 0xFFFFFF0110058071 的巨型结构。如果我们以 Little Endian 格式将其存储回内存,我们将得到 0x7180051001FFFFFF,这正是我所说的我们看到的数据。从理论上讲,Big Endian 会按照我假设的顺序(0x1700581001FFFFFF)进行解释和存储。
嗯,这对我来说很有意义。希望这对试图弄清楚同样事情的其他人有意义!我仍然不明白为什么他们都在他们之前说 LONGWORD...
所以,我可以理解为0x1234的一个字,当存储为little-endian时,在内存中变成0x3412。我还看到字节 0x12 作为位域 a:4 并且 b:4 将存储为 0x21。但是,如果我有更复杂的东西怎么办?数据如 0x1700581001FFFFFF 具有以下结构排序?我看到数据存储为 0x7180051001FFFFFF,这对我来说意义不大。似乎 'a' 和 'b' 被交换了,但它们仍保留在结构的开头,而 g 与其他看似随机的交换一起保留在末尾。为什么?另外,我留下了 "LONGWORD" 表示,因为它在代码中。我不确定 4 位如何成为长字,但也许这与这种疯狂有关?
LONGWORD a: 4
LONGWORD b: 4
LONGWORD c: 4
LONGWORD d: 12
LONGWORD e: 8
LONGWORD f: 8
LONGWORD g: 24
在 "implementation-defined manner"。根据 6.7.2.1 结构和联合说明符,C Standard 的第 11 段:
An implementation may allocate any addressable storage unit large enough to hold a bit-field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.
回答你的问题但是如果我有更复杂的东西怎么办?类似 0x1700581001FFFFFF 的数据具有以下结构排序?
在这种情况下,如果您想要可移植且可靠的代码,正确的答案是不使用位域。事实上,您未能在您的问题中提供足够的信息让任何人提供关于如何将数据放入您描述的位域的答案,这应该告诉您使用位域时出现的问题。
例如,给定您的位域
LONGWORD a: 4
LONGWORD b: 4
LONGWORD c: 4
LONGWORD d: 12
LONGWORD e: 8
LONGWORD f: 8
LONGWORD g: 24
如果假设位字段使用 16 位 int
类型的值,那么这样布置数据将是完全正确的:
16-bit `int` with `c`,`b`,`a` - in that order
16-bit `int` with `d`
16-bit `int` with `f`,`e` - in that order
16-bit `int` with first 16 bits of `g`
16-bit `int` with last 8 bits of `g` - **after** 8 bits of padding.
这甚至还没有进入存储的字节顺序。
诸如此类的问题(以及关于如何 "designate, in order, the meaning of bits to data" 的评论中提出的观点)不可避免地归结为:您要对数据做什么?
如果您要声明一个数据结构以便一些 C 代码可以写入它,而其他 C 代码可以从中读取,您很少关心字节顺序或位域顺序(或填充,或对齐方式,或任何对齐方式)。
它变得棘手——非常棘手——是当你尝试获取该数据结构时,因为你的 C 编译器将它放置在内存中,并将其写入外部世界或从外部世界读取它。当您尝试这样做时,您最终不得不永远担心类型大小、字节顺序、填充、对齐以及分配位域的顺序。
其实要操心的事情太多了,把它们都钉下来太麻烦了,以至于很多人(包括我自己)建议干脆不要尝试定义可以直接读写的数据结构完全没有。
我的记忆是,big-endinan 机器的编译器倾向于以一种方式布置位域中的位,而对于 little-endian 则以另一种方式布置。但我永远记不起哪条路是哪条路。 (即使我认为我记得,你也不应该相信我。)如果出于某种原因你关心,你将不得不做我一直做的事情,即编写一些小测试程序来构造一些二进制文件数据并以十六进制打印出来,并弄清楚它是如何为您今天使用的 machine/compiler 组合完成的。 (当然,您还必须决定如何应对下周您的 machine/compiler 组合可能发生变化的可能性。)
重新阅读文档后,我没有看到以任何顺序打包位域的任何余地。确实有一个指定的顺序,但它取决于实现方式。但它仍然是可以确定的。简而言之,据我所见,它按顺序按 8 位为一组打包位。我们的 Little Endian 编译器(或者可能是某处的某个选项)的不同之处在于,位的串联将第一个定义的位放在下一个定义的位之后(即,使第一个定义的位不如下一个定义的位重要)。例如:
a:3 = 111 (binary)
b:4 = 0000 (binary)
c:9 = 011111111 (binary)
我们的 Little Endian 编译器(或者,也许还有一些其他选项)将从 'a' 中取出 3 位,并通过将 'a' 添加到 'b' 的末尾与 b 连接.我相信,这与 Big Endian 编译器所做的相反,后者会将 'a' 放在 'b' 之前。所以我推测这是执行此操作的字节顺序,但我们的将通过制作 ba 而不是 ab 来获得 0000111 的 7 位。然后它还需要来自 c 的一位来创建一个完整的 8。它需要 'c' 的最低有效位,即 1,并且之前的位再次附加到该新位的末尾。所以我们有 10000111。然后这个字节 0x87 被存储到内存中,并获取另外 8 位。在此示例中,接下来的 8 位是 01111111,因此它在 0x87 之后存储该字节 0x7F。所以在内存中我们现在有 0x877F。另一种方法(可能是 Big Endian)将以 0xE0FF 结束。如果将现在内存中的 0x877F 解释为 Little Endian 中的一个字,则值为 0x7F87 或二进制形式为 0111111110000111。这恰好与上述连接 'cba' 的数据结构完全相反。
所以让我们对我之前提供的数据进行相同的反向排序: (0x1700581001FFFFFF 应该被解析如下,但我想这可能并不明显,因为我假设它是一个大端结构)
LONGWORD a: 4 = 0x1
LONGWORD b: 4 = 0x7
LONGWORD c: 4 = 0x0
LONGWORD d: 12 = 0x058
LONGWORD e: 8 = 0x10
LONGWORD f: 8 = 0x01
LONGWORD g: 24 = 0xFFFFFF
使用我们的 Little Endian 配置,通过按 gfedcba 顺序连接,这可以解释为一个值为 0xFFFFFF0110058071 的巨型结构。如果我们以 Little Endian 格式将其存储回内存,我们将得到 0x7180051001FFFFFF,这正是我所说的我们看到的数据。从理论上讲,Big Endian 会按照我假设的顺序(0x1700581001FFFFFF)进行解释和存储。
嗯,这对我来说很有意义。希望这对试图弄清楚同样事情的其他人有意义!我仍然不明白为什么他们都在他们之前说 LONGWORD...