Arithmetic right shift gives bogus result?

2019-01-07 20:31发布

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?

2条回答
唯我独甜
2楼-- · 2019-01-07 21:10

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. [...]

查看更多
Melony?
3楼-- · 2019-01-07 21:15

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.

查看更多
登录 后发表回答