我有一个包含两个Int32的Int64:
[StructLayout(LayoutKind.Explicit)]
public struct PackedInt64
{
[FieldOffset(0)]
public Int64 All;
[FieldOffset(0)]
public Int32 First;
[FieldOffset(4)]
public Int32 Second;
}
现在我想要构造函数(对于所有,第一和第二)。但是,struct需要在退出构造函数之前分配所有字段。 考虑 all 构造函数。
public PackedInt64(Int64 all)
{
this.First = 0;
this.Second = 0;
Thread.MemoryBarrier();
this.All = all;
}
我希望绝对确保在构造函数中最后分配this.All
,以便在cpu中进行某些编译器优化或指令重新排序时,不会覆盖该字段的一半或更多字段。
Thread.MemoryBarrier()
是否足够?这是最好的选择吗?
答案 0 :(得分:13)
是,这是防止重新排序的正确和最佳方法。
通过在示例代码中执行Thread.MemoryBarrier()
,永远不会允许处理器重新排序指令,以便在访问后访问/修改First
或Second
/修改为All
。由于它们都占用相同的地址空间,因此您不必担心以后的更改会被您之前的更改覆盖。
请注意Thread.MemoryBarrier()
仅适用于当前正在执行的线程 - 它不是一种锁。但是,鉴于此代码在构造函数中运行,并且没有其他线程可以访问此数据,这应该是完全正常的。但是,如果确实需要跨线程保证操作,则需要使用锁定机制来保证独占访问。
请注意,您可能实际上并不需要在基于x86的计算机上使用此指令,但如果您有一天在另一个平台上运行(例如IA64),我仍会推荐该代码。请参阅下面的图表,了解哪些平台将在保存后重新排序内存,而不仅仅是后加载。
答案 1 :(得分:3)
MemoryBarrier
会阻止重新排序,但此代码仍然存在。
LayoutKind.Explicit
和FieldOffsetAttribute
被记录为影响对象传递给非托管代码时的内存布局。它可用于与C union
互操作,但不能用于模拟C union
。
即使它目前按照您期望的方式运行,但在您测试的平台上,也无法保证它会继续这样做。唯一的保证是在与非托管代码互操作的上下文中(即p / invoke,COM interop或C ++ / CLI它只是工作)。
如果要以便携式面向未来的方式读取字节子集,则必须使用按位运算或字节数组BitConverter
。即使语法不太好。
答案 2 :(得分:2)
查看以下链接的评论部分:http://msdn.microsoft.com/en-us/library/system.threading.thread.memorybarrier.aspx
它说只有在内存排序较弱的多处理器系统上才需要MemoryBarrier()
。因此,这是一个足够的选择,但这是否是最佳选择取决于您使用的系统。
答案 3 :(得分:0)
首先,我知道这个答案并没有真正解决重新排序问题,而是否定了它。通过使用不安全的代码,您可以避免完全写入First
和Second
。
public unsafe PackedInt64(long all) {
fixed (PackedInt64* ptr = &this)
*(long*) ptr = all;
}
这并不是最优雅的解决方案,可能无法通过大多数有关托管代码的公司政策,但它应该有效。