Is masking before unsigned left shift in C/C++ too paranoid?
Solution 1:
Speaking to the C side of the problem,
- Is my reasoning correct, and is this a legitimate problem in theory?
It is a problem that I had not considered before, but I agree with your analysis. C defines the behavior of the <<
operator in terms of the type of the promoted left operand, and it it conceivable that the integer promotions result in that being (signed) int
when the original type of that operand is uint32_t
. I don't expect to see that in practice on any modern machine, but I'm all for programming to the actual standard as opposed to my personal expectations.
- Is this problem safe to ignore because on every platform the next integer type is double the width?
C does not require such a relationship between integer types, though it is ubiquitous in practice. If you are determined to rely only on the standard, however -- that is, if you are taking pains to write strictly conforming code -- then you cannot rely on such a relationship.
- Is a good idea to correctly defend against this pathological situation by pre-masking the input like this?: b = (a & 1) << 31;. (This will necessarily be correct on every platform. But this could make a speed-critical crypto algorithm slower than necessary.)
The type unsigned long
is guaranteed to have at least 32 value bits, and it is not subject to promotion to any other type under the integer promotions. On many common platforms it has exactly the same representation as uint32_t
, and may even be the same type. Thus, I would be inclined to write the expression like this:
uint32_t a = (...);
uint32_t b = (unsigned long) a << 31;
Or if you need a
only as an intermediate value in the computation of b
, then declare it as an unsigned long
to begin with.
Solution 2:
Q1: Masking before the shift does prevent undefined behavior that OP has concern.
Q2: "... because on every platform the next integer type is double the width?" --> no. The "next" integer type could be less than 2x or even the same size.
The following is well defined for all compliant C compilers that have uint32_t
.
uint32_t a;
uint32_t b = (a & 1) << 31;
Q3: uint32_t a; uint32_t b = (a & 1) << 31;
is not expected to incur code that performs a mask - it is not needed in the executable - just in the source. If a mask does occur, get a better compiler should speed be an issue.
As suggested, better to emphasize the unsigned-ness with these shifts.
uint32_t b = (a & 1U) << 31;
@John Bollinger good answer well details how to handle OP's specific problem.
The general problem is how to form a number that is of at least n
bits, a certain sign-ness and not subject to surprising integer promotions - the core of OP's dilemma. The below fulfills this by invoking an unsigned
operation that does not change the value - effective a no-op other than type concerns. The product will be at least the width of unsigned
or uint32_t
. Casting, in general, may narrow the type. Casting needs to be avoided unless narrowing is certain to not occur. An optimization compiler will not create unnecessary code.
uint32_t a;
uint32_t b = (a + 0u) << 31;
uint32_t b = (a*1u) << 31;
Solution 3:
Taking a clue from this question about possible UB in uint32 * uint32
arithmetic, the following simple approach should work in C and C++:
uint32_t a = (...);
uint32_t b = (uint32_t)((a + 0u) << 31);
The integer constant 0u
has type unsigned int
. This promotes the addition a + 0u
to uint32_t
or unsigned int
, whichever is wider. Because the type has rank int
or higher, no more promotion occurs, and the shift can be applied with the left operand being uint32_t
or unsigned int
.
The final cast back to uint32_t
will just suppress potential warnings about a narrowing conversion (say if int
is 64 bits).
A decent C compiler should be able to see that adding zero is a no-op, which is less onerous than seeing that a pre-mask has no effect after an unsigned shift.