次の方法で共有


Bit twiddling: What does warning CS0675 mean?

From the sublime level of continuation passing style we go back to the mundane level of twiddling individual bits.

int i = SomeBagOfBits();
ulong u = SomeOtherBagOfBits();
ulong result = u | i; // combine them together

Whoops, that's an error. "Operator | cannot be applied to operands of type int and ulong." There are bitwise-or operators defined on int, uint, long and ulong, but none between int and ulong. You cannot use the int version because the ulong might not fit, and you cannot use the ulong version because the int might be negative.

I demand that the compiler do my bidding regardless!

ulong result = u | (ulong) i;

There, now the compiler has to choose the ulong operator, and the explicit conversion from int to ulong we know never fails.

Argh, now we've got a warning! "CS0675: Bitwise-or operator used on a sign-extended operand; consider casting to a smaller unsigned type first."

I am often asked what the meaning of this warning is. The crux of the matter is that the conversion from int to ulong does sign extension. Let's make a more concrete example:

int i = -17973521; // In hex that is FEEDBEEF
ulong u = 0x0123456700000000;
ulong result = u | (ulong)i;
Console.WriteLine(result.ToString("x"));

What is the expected result? Most people expect that the result is 1234567FEEDBEEF. It is not. It is FFFFFFFFFEEDBEEF. Why? Because when converting an int to a ulong, first the int is converted to a long so that the sign information is not lost. The long -17973521 is in hex FFFFFFFFFEEDBEEF. That long is then converted to that ulong, which is then or'd in the natural way to produce the unexpected result.

The compiler warns you in this case because this pattern is almost always wrong. To eliminate the warning, first decide whether you want the sign extension or not. If you do not want it, then follow the advice given in the warning. The warning text says "consider casting to a smaller unsigned type" for a reason!

ulong result = u | (uint)i;

This tells the compiler "first convert the int to a uint then convert the uint to a ulong". Since all the math is now done in unsigned types, there is no sign to extend.

If for some strange reason what you want is the sign extension semantics then tell the compiler that explicitly:

ulong result = u | (ulong)(long)i;

And add a comment pointing out why you are doing this crazy thing, please.

However, better to not get into this bizarre situation altogether. First off, if you can avoid bit twiddling, do so. I very rarely have to twiddle a bit in a "primitive" integral type these days. Use enums with the Flags attribute if you want to represent a set of bit flags in a compact space. Second, if you must use primitive types then don't be doing bitwise operations on signed values; that is almost never the right thing to do. Third, try to avoid doing bitwise operations on operands of different bit size; that is also waving a red flag.

Comments

  • Anonymous
    November 29, 2010
    The comment has been removed

  • Anonymous
    November 29, 2010
    Ah yes, the 6809.  That was a fun little processor, wasn't it? I think I can count on one hand the number of times I've had to resort to bit twiddling in C# in the past 8 years.  I always err on the side of redundant casts and parentheses when twiddling bits, just to make sure that I, the compiler and any future reader share a common understanding of exactly what's being done.  

  • Anonymous
    November 29, 2010
    The comment has been removed

  • Anonymous
    November 29, 2010
    The comment has been removed

  • Anonymous
    November 29, 2010
    The comment has been removed

  • Anonymous
    November 29, 2010
    The comment has been removed

  • Anonymous
    November 29, 2010
    This would be a good moment to remind the readers of the BitArray class in .Net

  • Anonymous
    November 29, 2010
    @brian thatt only goes to the byte level, not bit. ;)

  • Anonymous
    November 29, 2010
    thx for your advice. @Simon: it's not about the hashcode, the array creation is too expensive. int/long/struct make the fastest dict keys, then string, int tuple, then int array. So we work with long, or string if the key doesn't fit in 64b.

  • Anonymous
    November 29, 2010
    "Use enums with the Flags attribute ... compact space" This is an attempt at optimizing for space. Programmers learned to do this when every bit was precious. Today it's just premature optimization. We write more code and create more bugs and deny the compiler the opportunity to optimize for us and give up much of the help of IntelliSense. I always encapsulate in a new type and store the various flags as separate bool fields. It's easy to go back later and change the storage to [Flags] enum or int or whatever, if that turns out to be critical.

  • Anonymous
    November 29, 2010
    The comment has been removed

  • Anonymous
    November 30, 2010
    @fowl: Yes, I was providing a suggestion for how to implement "some standard helper functions for producing an Int64 from two Int32, and similar stuff."

  • Anonymous
    November 30, 2010
    The comment has been removed

  • Anonymous
    November 30, 2010
    there's nothing stopping you adding such a set of structs, with the appropriate explict conversion operators and operator overloads. You could then add something like an FXCop rule which scanned for uses of the bitwise operators outside of these structs and treated them as errors. IT is annoyng that you would not be able to do this generically (Bitwise<T>) in the current system but you could generate them from a T4 template without much effort (and you'd really only need to do it for the relevant bit sizes, 8,16,32,64) though having  well defined operations on Bitwise32 and Bitwise64 would need rather more sophisticated templates so you might find doing it by hand was simpler.

  • Anonymous
    December 01, 2010
    The comment has been removed

  • Anonymous
    December 01, 2010
    I can't  believe nobody has mentioned BitVector. While I don't normally advocate stuffing multiple values into 32 bits, it has been invaluable for that legacy code that did so and needs to be maintained. In other maintenance code nightmares, never ever use Math.Pow() and/or don't ever calculate powers of 2 if all you are doing is: a)  calling Math.Pow(2, x /* someInt < 64*/) frequently b)  just testing bit positions. Use a lookup table instead.

  • Anonymous
    December 09, 2010
    for: Color color = Color.FromARGB(255,255,255); uint r=(uint)((uint)color.B<<16|(ushort)color.R<<8|color.G); why uint r=(uint)((uint)color.B<<16|(ushort)color.R<<8|color.G);   cause warning cs0675 but (uint)(color.B<<16|(ushort)color.R<<8|color.G); or (uint)((sbyte)color.B<<16|(ushort)color.R<<8|color.G); does not cause warning? i think there is no sign-extented in the former situation.