32

I must be absolutely crazy here, but gcc 4.7.3 on my machine is giving the most absurd result. Here is the exact code that I'm testing:

#include <iostream>

using namespace std;

int main(){
  unsigned int b = 100000;
  cout << (b>>b) << endl;
  b = b >> b;
  cout << b << endl;
  b >>= b;
  cout << b << endl;
  return 0;
}

Now, any number that's right shifted by itself should result in 0 (n/(2^n) == 0 with integer divide, n>1, and positive/unsigned), but somehow here is my output:

100000
100000
100000

Am I crazy? What could possibly be going on?

Failed Scientist
  • 1,977
  • 3
  • 29
  • 48
Suedocode
  • 2,504
  • 3
  • 23
  • 41
  • @ShafikYaghmour: That assumes the compiler even bothers putting in an instruction. It's well within its right to just reject this program. – MSalters Oct 28 '13 at 16:06
  • @MSalters indeed, we are getting into compiler/platform/version specific at this point but for current and recent versions this is the case and as I already stated it is undefined so you are clearly on your own, `gcc` only seems to produce a `shr` when using `-O0`. – Shafik Yaghmour Oct 28 '13 at 16:12
  • @ShafikYaghmour: Intel is just one of the many platforms supported by gcc, and they have different optimization stages. A common trick in optimization is to say "This value can only be between 0 and 31 because it's used in a shift, if I follow code path X to get here the value would not be between 0 and 31, so code path X is impossible and I don't even need to generate instructions for it". GCC famously does this for null pointer checks. – MSalters Oct 28 '13 at 16:18
  • @MSalters that makes sense, so this would effect warnings as well? – Shafik Yaghmour Oct 28 '13 at 16:26
  • @ShafikYaghmour: Hopefully, but the experience with GCC silently removing flawed null pointer checks tells us that you can't rely on it. – MSalters Oct 28 '13 at 16:29
  • @davmac: Compilers can and do optimize on future _unavoidable_ undefined behavior. It gets really interesting when externally visible side effects become involved. – MSalters Oct 28 '13 at 16:46
  • > Comments are becoming a discussion, please ask a new question. – MSalters Oct 28 '13 at 16:52
  • 5
    A fun fact: though this is *undefined* behavior in C, it is *defined* behavior in C#, but the definition is somewhat bizarre. In C#, `x>>y` for 32 bit ints x and y is computed as `x>>(y&0x1f)` ! So `(x >> 16) >> 16` is zero, but `x >> 32` is x. – Eric Lippert Oct 28 '13 at 20:51

2 Answers2

49

In C++ as in C, shifts are limited to the size (in bits) of the value shifted. For instance, if unsigned int is 32 bits, then a shift of more than 31 is undefined.

In practice, a common result is that the 5 least-significant bits of the shift amount are used and the higher order bits are ignored; this is due to the compiler producing a machine instruction which does exactly that (eg SHR on x86).

In this case, the shift value is 100000 (decimal) which happens to be 11000011010100000 in binary - the lower 5 bits are zero. So, you're effectively getting a shift by 0. You shouldn't rely on this however; technically, what you're seeing is undefined behaviour.

References:

For C, N1570 section 6.5.7:

If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

For C++, N3690 section 5.8 "[expr.shift]":

The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.

N1570 is a draft, nearly identical to the released ISO C11 standard; this clause has been pretty much the same since the 1989 ANSI C standard.

N3690 is a recent draft of the C++ standard; I'm not sure whether it's the best one to use, but again, this clause hasn't changed.

davmac
  • 20,150
  • 1
  • 40
  • 68
  • 1
    Did you observe this behavior with different compilers or read from some where? – Grijesh Chauhan Oct 28 '13 at 13:56
  • 3
    @GrijeshChauhan, this is documented in the C and C++ standard specifications. Regarding generation of the SHR instruction, I have observed this. – davmac Oct 28 '13 at 13:57
  • Holy mother of coding... I just tested this and the binary of 100000 is `0b110000110101_00000`. I tried shifting by 100001, and it indeed shifted by 1. – Suedocode Oct 28 '13 at 14:05
  • 2
    @Aggieboy It will also depend other things such as optimization level `gcc` and `clang` have different output at different optimization levels which is completely consistent with undefined behavior i.e. anything can really happen. – Shafik Yaghmour Oct 28 '13 at 14:20
32

You are invoking undefined behavior if you shift greater than the bit length of the left operand, the draft C++ standard section 5.8 Shift operators paragraph 1 says(emphasis mine):

The operands shall be of integral or unscoped enumeration type and integral promotions are performed. The type of the result is that of the promoted left operand. The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.

Interesting to note that both gcc and clang may generate a warning for this code if the shift amount if a literal:

cout << (b>> 100000) ;

or if b is a const, the warning for gcc is as follows:

warning: right shift count >= width of type [enabled by default]

as MSalters points out in the comments to the question, we may not be able to even rely on this warning since this is undefined behavior, which is consistent with the standards note on undefined behavior in the terms and definitions section which says:

Note: [...] Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). [...]

Platform specific details

A potential explanation for the apparent lack of a shift in the example code may be because on some platforms the shift count will be masked to 5 bits for example on an x86 architecture we can see the Intel® 64 and IA-32 Architectures Software Developer’s Manual section SAL/SAR/SHL/SHR—Shift in the IA-32 Architecture Compatibility section says:

The 8086 does not mask the shift count. However, all other IA-32 processors (starting with the Intel 286 processor) do mask the shift count to 5 bits, resulting in a maximum count of 31. [...]

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • The 80286 deliberately allowed shift amounts up to the word size, inclusive. I wonder why the 80386 did not do likewise (using 6 bits of CL when the operand size was 32 bits)? – supercat Aug 26 '16 at 23:29
  • Also see [this blog post](https://david.wragg.org/blog/2012/11/shift-instructions.html) for more background on x86. – Shafik Yaghmour Jun 01 '17 at 19:43