问题
I have some code like the following code block (I am not allowed to post the original code) inside a .cpp file that I think is being compiled by clang++ (Ubuntu clang version 3.5.2-3ubuntu1 (tags/RELEASE_352/final) (based on LLVM 3.5.2)).
It looks like C code, because we are using GoogleTest for testing our C code. Anyways:
size_t const SHIFT = 4;
uint8_t var, var2;
/* Omitted: Code that sets var to, say 00011000 (base 2) */
var2 = var;
var = var << SHIFT >> SHIFT; // [1] result is 00011000 (base 2) (tested with printf)
var2 = var2 << SHIFT;
var2 = var2 >> SHIFT; // [2] result is 00001000 (base 2) (tested with printf)
Now, why does comment [1] hold true? I was assuming that the corresponding line would result in the top 4 bits being zeroed out. But I found out that this isn't true; the program simply restores the original value.
Is this some language defined behavior, or is
clangcompiling out a supposedly useless bit shift?
(I checked associativity (using this table on cppreference.com, assuming that associativity/precedence of basic operators won't differ between versions of C++ and probably not between C++ and C either, at least not in 'current versions') and it seems like the RHS expression at [1] should really yield the same result as the two following statements)
回答1:
What you are seeing is a result of integer promotions. Any value with a type of lower rank than int, when used in an expression, is promoted to int.
This is detail in section 6.3.1.1 of the C standard
2 The following may be used in an expression wherever an
intorunsigned intmay be used:
- An object or expression with an integer type (other than
intorunsigned int) whose integer conversion rank is less than or equal to the rank ofintandunsigned int.- A bit-field of type
_Bool,int,signed int, orunsigned int.If an
intcan represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to anint; otherwise, it is converted to anunsigned int. These are called the integer promotions. All other types are unchanged by the integer promotions.
In this case:
var = var << SHIFT >> SHIFT;
var is first promoted to int. This type is at least 16 bits wide, and most likely 32 bits wide. So the value being operated on is 0x00000018. A left shift of 4 results in 0x00000180, and subsequently right shifting results in 0x00000018.
The result is then stored in a uint_8. Since the value fits in a variable of this type, no conversion is required and 0x18 is stored.
回答2:
The expression:
var = var << SHIFT >> SHIFT;
is not semantically equivelent to
var = var << SHIFT ;
var = var >> SHIFT ;
That would require:
var = (uint8_t)(var << SHIFT) >> SHIFT ;
which illustrates the effective difference between the two - the observed behaviour does not require optimisation, rather it is required by language definition with respect to type promotion rules in expressions.
That said, it is also entirely possible that a compiler could optimise out the shifts. Optimisation may not change the result where the behaviour is defined as it is in this case.
来源:https://stackoverflow.com/questions/48775746/do-compilers-optimize-out-net-zero-bit-shifts