结构字段布局是否与 C# 中的字节顺序一致?
Is struct field layout consistent with endianness in C#?
当我第一次学习字节顺序时,我对它的工作原理感到非常困惑。最后我用下面的比喻给自己解释了一下:
在大端机器上,int[4]
会这样排列:
| int[4] |
|int1|int2|int3|int4|
在小端机器上,它的布局类似于
| int[4] |
|1tni|2tni|3tni|4tni|
这样数组的布局在内存中就会保持一致,而值本身的排列方式会有所不同。
现在开始真正的问题: 我正在我的 .NET 库中编写 BinaryReader
和 BinaryWriter
的更优化版本。 运行 我遇到的问题之一是 Write(decimal)
的实现。一个小数包含 4 个 int
字段:flags, hi, lo,
和 mid,
in that order. 所以基本上在典型的小端机器上它在内存中看起来像这样:
| lamiced |
|sgalf|ih|ol|dim|
我的问题是,CLR 将如何在大端机器上安排结构?会不会这样安排,让小数点的基本布局得以保留,像这样
| decimal |
|flags|hi|lo|mid|
还是会完全颠倒小数的二进制排列,比如
| decimal |
|mid|lo|hi|flags|
?
附近没有big-endian机器,不然我自己试一下
编辑: TL;DR 以下代码是否在大端机器上打印 -1
或 0
?
struct Pair
{
public int a;
public int b;
}
unsafe static void Main()
{
var p = default(Pair);
p.a = -1;
Console.WriteLine(*(int*)&p);
}
不完全清楚你的实际问题是什么。
关于数据结构中字段的布局与字节序的关系,有none。字节顺序不会影响数据结构中字段的布局方式,只会影响字段中字节的顺序。
即对此的回答:
does the following code print -1 or 0 on big-endian machines?
…输出将是-1
.
但您似乎也在询问字节顺序对 Decimal
类型在内存中表示的影响。这是一个有点不同的问题。
关于 Decimal
内存表示的字节顺序,我不知道 .NET 提供 Decimal
类型的一致实现的任何要求。正如评论者 Hans Passant 指出的那样,有多种方法可以查看当前的实现;作为您引用的 CLR 代码,或者作为更详细的声明,例如wtypes.h 或 OleDb.h (另一个地方出现了 DECIMAL
类型,其格式与其他地方相同)。但实际上,就 .NET 而言,您没有得到关于该类型的内存布局的任何承诺。
我希望,为了简化实现,表示 3 个 32 位尾数分量的字段可能会受到字节顺序的影响,单独。 (符号和比例表示为单独的字节,因此字节序不会影响它们)。也就是说,虽然各个 32 位字段的顺序将保持不变 — 高、低、中 — 每个字段中的字节将根据当前平台的字节顺序表示。
但是 如果 Microsoft 出于某种奇怪的原因决定他们希望 .NET 实现偏离本机实现(似乎不太可能,但为了争论起见我们假设它)并且始终使用小端对于即使在大端平台上的字段,这也是他们的权利。
就此而言,如果他们愿意,他们甚至可以重新排列字段:在我看来,他们当前的顺序似乎是对事实上的 x86 小端标准的让步,这样在小端架构上组合低位和中位 32 位值可以被视为单个 64 位值而无需交换字,因此如果他们决定偏离 wtypes.h 声明,他们很可能会决定只将尾数设为单个 96-位、小端或大端值。
再说一次,我并不是说这些行为在任何方面都是可能的。只是它们在理论上是可能的,并且只是简单、明显的示例(所有可能示例的子集),说明为什么编写假定此类私有实现细节的托管代码可能不是一个好主意。
即使您可以访问可以 运行 .NET 库 (*) 并因此可以测试实际行为的大端计算机,今天的当前行为也不能为您提供未来行为的任何保证.
(*)(我什至不知道有什么……现在纯大端 CPU 相当罕见,我想不出一个Microsoft 支持它作为实际的 .NET 平台。)
所以……
我怀疑编写 BinaryReader
和 BinaryWriter
的实现明显比 .NET 中的实现更优化。使用这些类型的主要原因是处理 I/O,这必然意味着与外部系统交互,这些系统比 CPU 处理实际的字节表示转换(和甚至支持这些转换的 GC 操作)。即使现有的 Microsoft 代码在某种程度上假设效率低下,但在实践中我怀疑它是否重要。
但是如果你必须自己实现这些,在我看来,处理 Decimal
类型的唯一安全方法是使用 Decimal.GetBits()
方法和 Decimal.Decimal(int[])
构造函数。这些使用明确记录的、字节序无关的机制来转换 Decimal
类型。它们基于 int
,其在内存中的表示形式当然会根据字节顺序而有所不同,但您的代码永远不需要担心这一点,因为它只需要处理整个 int
值,而不是它们的字节表示形式。
当我第一次学习字节顺序时,我对它的工作原理感到非常困惑。最后我用下面的比喻给自己解释了一下:
在大端机器上,int[4]
会这样排列:
| int[4] |
|int1|int2|int3|int4|
在小端机器上,它的布局类似于
| int[4] |
|1tni|2tni|3tni|4tni|
这样数组的布局在内存中就会保持一致,而值本身的排列方式会有所不同。
现在开始真正的问题: 我正在我的 .NET 库中编写 BinaryReader
和 BinaryWriter
的更优化版本。 运行 我遇到的问题之一是 Write(decimal)
的实现。一个小数包含 4 个 int
字段:flags, hi, lo,
和 mid,
in that order. 所以基本上在典型的小端机器上它在内存中看起来像这样:
| lamiced |
|sgalf|ih|ol|dim|
我的问题是,CLR 将如何在大端机器上安排结构?会不会这样安排,让小数点的基本布局得以保留,像这样
| decimal |
|flags|hi|lo|mid|
还是会完全颠倒小数的二进制排列,比如
| decimal |
|mid|lo|hi|flags|
?
附近没有big-endian机器,不然我自己试一下
编辑: TL;DR 以下代码是否在大端机器上打印 -1
或 0
?
struct Pair
{
public int a;
public int b;
}
unsafe static void Main()
{
var p = default(Pair);
p.a = -1;
Console.WriteLine(*(int*)&p);
}
不完全清楚你的实际问题是什么。
关于数据结构中字段的布局与字节序的关系,有none。字节顺序不会影响数据结构中字段的布局方式,只会影响字段中字节的顺序。
即对此的回答:
does the following code print -1 or 0 on big-endian machines?
…输出将是-1
.
但您似乎也在询问字节顺序对 Decimal
类型在内存中表示的影响。这是一个有点不同的问题。
关于 Decimal
内存表示的字节顺序,我不知道 .NET 提供 Decimal
类型的一致实现的任何要求。正如评论者 Hans Passant 指出的那样,有多种方法可以查看当前的实现;作为您引用的 CLR 代码,或者作为更详细的声明,例如wtypes.h 或 OleDb.h (另一个地方出现了 DECIMAL
类型,其格式与其他地方相同)。但实际上,就 .NET 而言,您没有得到关于该类型的内存布局的任何承诺。
我希望,为了简化实现,表示 3 个 32 位尾数分量的字段可能会受到字节顺序的影响,单独。 (符号和比例表示为单独的字节,因此字节序不会影响它们)。也就是说,虽然各个 32 位字段的顺序将保持不变 — 高、低、中 — 每个字段中的字节将根据当前平台的字节顺序表示。
但是 如果 Microsoft 出于某种奇怪的原因决定他们希望 .NET 实现偏离本机实现(似乎不太可能,但为了争论起见我们假设它)并且始终使用小端对于即使在大端平台上的字段,这也是他们的权利。
就此而言,如果他们愿意,他们甚至可以重新排列字段:在我看来,他们当前的顺序似乎是对事实上的 x86 小端标准的让步,这样在小端架构上组合低位和中位 32 位值可以被视为单个 64 位值而无需交换字,因此如果他们决定偏离 wtypes.h 声明,他们很可能会决定只将尾数设为单个 96-位、小端或大端值。
再说一次,我并不是说这些行为在任何方面都是可能的。只是它们在理论上是可能的,并且只是简单、明显的示例(所有可能示例的子集),说明为什么编写假定此类私有实现细节的托管代码可能不是一个好主意。
即使您可以访问可以 运行 .NET 库 (*) 并因此可以测试实际行为的大端计算机,今天的当前行为也不能为您提供未来行为的任何保证.
(*)(我什至不知道有什么……现在纯大端 CPU 相当罕见,我想不出一个Microsoft 支持它作为实际的 .NET 平台。)
所以……
我怀疑编写 BinaryReader
和 BinaryWriter
的实现明显比 .NET 中的实现更优化。使用这些类型的主要原因是处理 I/O,这必然意味着与外部系统交互,这些系统比 CPU 处理实际的字节表示转换(和甚至支持这些转换的 GC 操作)。即使现有的 Microsoft 代码在某种程度上假设效率低下,但在实践中我怀疑它是否重要。
但是如果你必须自己实现这些,在我看来,处理 Decimal
类型的唯一安全方法是使用 Decimal.GetBits()
方法和 Decimal.Decimal(int[])
构造函数。这些使用明确记录的、字节序无关的机制来转换 Decimal
类型。它们基于 int
,其在内存中的表示形式当然会根据字节顺序而有所不同,但您的代码永远不需要担心这一点,因为它只需要处理整个 int
值,而不是它们的字节表示形式。