Freigeben über


What's the Difference? Part Three: fixed vs. fixed

I got an email the other day that began:

I have a question about fixed sized buffers in C#:  

unsafe struct FixedBuffer { public fixed int buffer[100]; }

Now by declaring buffer as fixed it is not movable...

And my heart sank. This is one of those deeply unfortunate times when subtle choices made in the details of language design encourage misunderstandings.

When doing pointer arithmetic in unsafe code on a managed object, you need to make sure that the garbage collector does not move the memory you're looking at. If a collection on another thread happens while you're doing pointer arithmetic on an object, the pointer can get all messed up. Therefore, C# classifies all variables as "fixed" or "movable". If you want to do pointer arithmetic on a movable object, you can use the "fixed" keyword to say "this local variable contains data which the garbage collector should not move." When a collection happens, the garbage collector needs to look at all the local variables for in-flight calls (because of course, stuff that is in local variables needs to stay alive); if it sees a "fixed" local variable then it makes a note to itself to not move that memory, even if that fragments the managed heap. (This is why it is important to keep stuff fixed for as little time as possible.) So typically, we use "fixed' to mean "fixed in place".

But that's not what "fixed" means in this context; this means "the buffer in question is fixed in size to be one hundred ints" -- basically, it's the same as generating one hundred int fields in this structure.

Obviously we often use the same keyword to mean conceptually the same thing. For example, we use the keyword "internal" in many ways in C#, but all of them are conceptually the same. It is only ever used to mean "accessibility to some entity is unrestricted to all code in this assembly".

Sometimes we use the same keyword to mean two completely different things, and rely upon context for the user to figure out which meaning is intended. For example:

var results = from c in customers where c.City == "London" select c;

vs

class C<T> where T : IComparable<T>

It should be clear that "where" is being used in two completely different ways: to build a filter clause in a query, and to declare a type constraint on a generic type parameter.

We cause people to run into trouble when one keyword is used in two different ways but the difference is subtle, like our example above. The user's email went on to ask a whole bunch of questions which were predicated on the incorrect assumption that a fixed-in-size buffer is automatically fixed in place in memory.

Now, one could say that this is just an unfortunate confluence of terms; that "fixed in size" and "fixed in place" just happen to both use the word "fixed" in two different ways, how vexing. But the connection is deeper than that: you cannot safely access the data stored in a fixed-in-size buffer unless the container of the buffer has been fixed in place. The two concepts are actually quite strongly related in this case, but not at all the same.

On the one hand it might have been less confusing to use two keywords, say "pinned" and "fixed". But on the other hand, both usages of "fixed" are only valid in unsafe code. A key assumption of all unsafe code features is that if you are willing to use unsafe code in C#, then you are already an expert programmer who fully understands memory management in the CLR. That's why we make you write "unsafe" on the code; it indicates that you're turning off the safety system and you know what you're doing.

A considerable fraction of the keywords of C# are used in two or more ways: fixed, into, partial, out, in, new, delegate, where, using, class, struct, true, false, base, this, event, return and void all have at least two different meanings. Most of those are clear from the context, but at least the first three -- fixed, into and partial -- have caused enough confusion that I've gotten questions about the differences from perplexed users. I'll take a look at "into" and "partial" next.

Comments

  • Anonymous
    August 27, 2009
    > A key assumption of all unsafe code features is that if you are willing to use unsafe code in C#, then you are already an expert programmer who fully understands memory management in the CLR. There's your problem right there - it's a bad assumption.  I bet a significant portion of programmers using unsafe code simply need to call into an unmanaged library somewhere and have no other choice but to muddle through.   I call unmanaged libraries all the time without using unsafe code. I use the marshaling attributes to make the p/invoke call work out right, rather than trying to work directly with pointers. -- Eric

  • Anonymous
    August 27, 2009
    One interesting design option would be to place "fixed" inside the brackets, same way C99 does for "static" to handle similar scenario:    public int buffer[fixed 100]; This way you could say that it clearly indicates that size is fixed, not the value of expression (buffer).

  • Anonymous
    August 27, 2009
    > It should be clear that "where" is being used in two completely different ways: to build a filter clause in a query, and to declare a type constraint on a generic type parameter. They're not that different: in both cases, "where" creates a constraints. In one case it's a type constraint, in the other one it's a value constraint (but could just as well be a type one). I think they are extremely different. The generic type constraint is about restricting what entire classes of things may even exist, at compile time. The filtering clause is about taking a set of existing objects and sorting them into two piles, at run time.Those seem like two completely different operations. -- Eric Also, you explanation wasn't quite clear. From it I infer "fixed" only means "of fixed size", but I don't quite get the point of the third-from-last paragraph, does it mean that the buffer is fixed-in-place in a safe context? No. Fixed-in-size buffers are illegal to use in any way in safe contexts. In an unsafe context you can use the "fixed" keyword to declare that a local variable refers to a fixed-in-memory structure. When you do so to a structure that contains a fixed-in-size buffer, it becomes legal to access the fixed-in-size buffer. So there we've got the confluence of the two kinds of "fixed" -- a fixed-in-size buffer can only be used when its container is fixed-in-place. -- Eric

  • Anonymous
    August 27, 2009
    This might be a silly question, but what's the second usage of void? void means either "this is a method that returns no value", or in "void*" it means "this is a pointer type but the pointer points to something of unknown size". -- Eric

  • Anonymous
    August 27, 2009
    @masklinn, thank you!  I thought I was the only one who thought that same thought.  I read the entire thing and at the end went... wait, did he actually describe each possible use of fixed and what the actual outcome was of the code provided?  I am assuming that fixed in the context provided mean the buffer was fixed in size, not in memory.  It would be nice to know how one would accomplish fixed in memory. I agree that it is unclear. I'll add another explanatory paragraph. -- Eric

  • Anonymous
    August 27, 2009
    masklinn, cpradio: Eric indeed didn't explain fixed-in-memory. That's the "normal" scenario for using fixed. For the buffer to be fixed in memory the variable that contains it should be declared as fixed, e.g. void DoSomething() {    fixed FixedBuffer buffer = Something(); // if I'm not mistaken this is the syntax, I've never actually used it.    UseBufferSomehowInUnsafeCode(buffer); }

  • Anonymous
    August 27, 2009
    @configurator: The only other usage I can think of is using void* in unsafe code.

  • Anonymous
    August 27, 2009
    Duh! void*... Makes sense I wouldn't think of it though... Quite a blast from the past, that is :) What about true and false? They mean either the Boolean literals, or "here are the methods that the generated code should call when doing short-circuit evaluation of && (or ||) on an argument whose type has user-defined overloaded & (or |) operator." -- Eric

  • Anonymous
    August 27, 2009
    I'm thinking for true and false Eric is referring to overloads of operator true and operator false, although there may be something else that is not occurring to me.

  • Anonymous
    August 27, 2009
    In the programming guide (http://msdn.microsoft.com/en-us/library/zycewsya(VS.80).aspx), it does say the following ... In C# 2.0, a struct can be declared with an embedded array: public struct MyArray // This code must appear in an unsafe block
    {
       public fixed char pathName[128];
    } In this structure, the pathName array is of fixed size and location, and can therefore be used with other unsafe code. Is this an error or does location have a different meaning in this context as well? That's an error. Thanks for bringing it to my attention! -- Eric

  • Anonymous
    August 27, 2009
    I think the use of the keyword fixed to indicate an array that is embedded in a structure is unfortunate. The key point of these declarations is not that the array is fixed-in-size, but that the array is embedded in a structure. So I think it had been much clearer if you had used the keywords inline or embedded instead. unsafe struct FixedBuffer { public fixed int buffer[100]; } would in my opinion be clearer as unsafe struct FixedBuffer { public inline int buffer[100]; } or unsafe struct FixedBuffer { public embedded int buffer[100]; } Or have I maybe misunderstood the meaning of the fixed declaration in this context ?

  • Anonymous
    August 27, 2009
    @JP I agree that that language is extremely poorly worded given the dual meaning of fixed.

  • Anonymous
    August 28, 2009
    While I would prefer that keywords not have confusing double meanings, each keyword is a name you can't use for a variable, class, or method.

  • Anonymous
    August 28, 2009
    I had a question, but I answered it.  In case anyone else is interested: I wondered why fixed size buffers weren't safe.  According to MSDN it's because accesses to the buffer aren't range checked.  Which (according to the language docs) is because accesses are through a pointer. Then I realised that the fundamental problem is the CLR doesn't directly support fixed size buffers.  So what are they?  Reflector to the rescue: a fixed size buffer declared like this: unsafe public fixed int buffer[100]; is actually of type: [StructLayout(LayoutKind.Sequential, Size=400), CompilerGenerated, UnsafeValueType] public struct <buffer>e__FixedBuffer0 {    public int FixedElementField; } where "<buffer>e__FixedBuffer0" is an internal compiler-generated name.  The StructLayoutAttribute size parameter tells the CLR to reserve 400 bytes (i.e. room for 100 ints) for this struct.  And the data is accessed using unsafe pointer arithmetic. I guess nobody thought fixed size buffers were sufficiently useful to add safe support for them to the CLR.

  • Anonymous
    August 28, 2009
    So if you wanted to both fix the size of the buffer to 100 ints, and to fix the buffer in memory, how would you go about it? Thanks.

  • Anonymous
    August 28, 2009
    Here is an simple horrible example: public class UnsafeBufferInt100 {    unsafe struct FixedBuffer { public fixed int buffer[100]; }    unsafe FixedBuffer fixedBuffer = default(FixedBuffer);    public int this[int i]    {        get        {            unsafe            {                fixed (int* buffer = fixedBuffer.buffer)                {                    return buffer[i];                }            }        }        set        {            unsafe            {                fixed (int* buffer = fixedBuffer.buffer)                {                    buffer[i] = value;                }            }        }    } }