Tell me more ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I have an Int64 containing two Int32 like this:

[StructLayout(LayoutKind.Explicit)]
public struct PackedInt64
{
    [FieldOffset(0)]
    public Int64 All;
    [FieldOffset(0)]
    public Int32 First;
    [FieldOffset(4)]
    public Int32 Second;
}

Now I want constructors (for all, first and second). However the struct requires all fields to be assigned before the constructor is exited. Consider the all constructor.

public PackedInt64(Int64 all)
{
    this.First = 0;
    this.Second = 0;
    Thread.MemoryBarrier();
    this.All = all;
}

I want to be absolutely sure that this.All is assigned last in the constructor so that half of the field or more isn't overwritten in case of some compiler optimization or instruction reordering in the cpu.

Is Thread.MemoryBarrier() sufficient? Is it the best option?

share|improve this question
What's the point of zeroing First and Second? – zmbq Jul 7 at 21:04
The point is that the compiler complains if you don't. It doesn't understand (in this case/context) that assigning All will assign the others as well. – Lasse V. Karlsen Jul 7 at 21:05
5  
I would use bitwise operations to pick out the 4 byte integers and do away with the nasty explicit layout. – David Heffernan Jul 7 at 21:08
@DavidHeffernan That's one way to look at it. However the explicit layout makes me feel all warm and fuzzy. :) – Carl R Jul 7 at 21:10
1  
This can be only reasoned-out if you give a good description of how another thread gets to access that structure. There is almost never a way to do so reliably unless a lock is taken. Either explicit by you or implicit in the OS when it starts another thread. Which in itself already takes care of the memory barrier requirement. – Hans Passant Jul 7 at 23:38
show 4 more comments

4 Answers

up vote 8 down vote accepted

Yes, this is the correct and best way of preventing reordering.

By executing Thread.MemoryBarrier() in your sample code, the processor will never be allowed to reorder instructions in such a way that the access/modification to First or Second will occur after the access/modification to All. Since they both occupy the same address space, you don't have to worry about your later changes being overwritten by your earlier ones.

Note that Thread.MemoryBarrier() only works for the current executing thread -- it isn't a type of lock. However, given that this code is running in a constructor and no other thread can yet have access to this data, this should be perfectly fine. If you do need cross-thread guarantee of operations, however, you'll need to use a locking mechanism to guarantee exclusive access.

Note that you may not actually need this instruction on x86 based machines, but I would still recommend the code in case you run on another platform one day (such as IA64). See the below chart for what platforms will reorder memory post-save, rather than just post-load.

enter image description here

share|improve this answer

Check the remarks section of the following link: http://msdn.microsoft.com/en-us/library/system.threading.thread.memorybarrier.aspx

It says MemoryBarrier() is required only on multiprocessor systems with weak memory ordering. So, this is a sufficient option but whether this is the best option or not depends upon the system you are using.

share|improve this answer
Writing good code means you need to account for environments you were not expecting. Since the code might one day run on a system with different memory ordering, it would be recommended to use the memory barrier. – David Pfeffer Jul 8 at 12:54
yes, I agree that's why I stated "whether this is the best option or not depends upon the system you are using" in my answer – Anandaraj Jul 8 at 13:10

The MemoryBarrier will prevent re-ordering, but this code is still broken.

LayoutKind.Explicit and FieldOffsetAttribute are documented as affecting the memory layout of the object when it is passed to unmanaged code. It can be used to interop with a C union, but it cannot be used to emulate a C union.

Even if it currently acts the way you expect, on the platform you tested, there is no guarantee that it will continue to do so. The only guarantee made is in the context of interop with unmanaged code (that is, p/invoke, COM interop, or C++/CLI it-just-works).

If you want to read a subset of bytes in a portable future-proof manner, you'll have to use bitwise operations or a byte array and BitConverter. Even if the syntax isn't as nice.

share|improve this answer
1  
It certainly can be used to emulate a C union. I used this fact in a blog post. – Kendall Frey Jul 8 at 13:18
   
"Use the attribute with LayoutKind.Sequential to force the members to be laid out sequentially in the order they appear. For blittable types, LayoutKind.Sequential controls both the layout in managed memory and the layout in unmanaged memory. For non-blittable types, it controls the layout when the class or structure is marshaled to unmanaged code, but does not control the layout in managed memory." msdn.microsoft.com/en-us/library/… Blittable vs Nonblittable types: msdn.microsoft.com/en-us/library/75dwhxf7.aspx – Carl R Jul 8 at 13:59
Kendall: Read my third paragraph again, please. – Ben Voigt Jul 8 at 14:38
@Carl: Maybe you were looking for this quote: "Use the attribute with LayoutKind.Explicit to control the precise position of each data member. This affects both managed and unmanaged layout, for both blittable and non-blittable types". I dunno what's right, at this point I'd want to see the formal language in the ECMA Standard for the Common Language Runtime to know what guarantees are made and what are implementation details in specific versions. – Ben Voigt Jul 8 at 14:41
@BenVoigt Yes, thanks! If you find any ecma standard documentation regarding this please share. :) – Carl R Jul 8 at 14:59

First, I'm aware this answer doesn't really solve the reordering problem, but negates it. By using unsafe code, you can avoid writing to First and Second completely.

public unsafe PackedInt64(long all) {
    fixed (PackedInt64* ptr = &this)
        *(long*) ptr = all;
}

It's not meant to be the most elegant solution and probably doesn't pass most company policies regarding managed code, but it should work.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.