21

I recently faced a strange behavior using the right-shift operator.

The following program:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <stdint.h>

int foo(int a, int b)
{
   return a >> b;
}

int bar(uint64_t a, int b)
{
   return a >> b;
}

int main(int argc, char** argv)
{
    std::cout << "foo(1, 32): " << foo(1, 32) << std::endl;
    std::cout << "bar(1, 32): " << bar(1, 32) << std::endl;
    std::cout << "1 >> 32: " << (1 >> 32) << std::endl; //warning here
    std::cout << "(int)1 >> (int)32: " << ((int)1 >> (int)32) << std::endl; //warning here

    return EXIT_SUCCESS;
}

Outputs:

foo(1, 32): 1 // Should be 0 (but I guess I'm missing something)
bar(1, 32): 0
1 >> 32: 0
(int)1 >> (int)32: 0

What happens with the foo() function ? I understand that the only difference between what it does and the last 2 lines, is that the last two lines are evaluated at compile time. And why does it "work" if I use a 64 bits integer ?

Any lights regarding this will be greatly appreciated !


Surely related, here is what g++ gives:

> g++ -o test test.cpp
test.cpp: In function 'int main(int, char**)':
test.cpp:20:36: warning: right shift count >= width of type
test.cpp:21:56: warning: right shift count >= width of type
4

7 に答える 7

37

CPUが実際にコンピューティングしている可能性があります

a >> (b % 32)

foo; 一方、1 >> 32は定数式であるため、コンパイラはコンパイル時に定数をフォールドします。これにより、どういうわけか0が返されます。

標準(C++98§5.8/1)には次のように記載されているため

右のオペランドが負の場合、またはプロモートされた左のオペランドのビット単位の長さ以上の場合、動作は未定義です。

異なる結果をもたらし、与えるfoo(1,32)矛盾はありません。1>>32

 

一方、bar64ビットの符号なし値を指定した場合、64> 32であるため、結果は1/2 32 = 0であることが保証されます。それでも、次のように記述した場合

bar(1, 64);

あなたはまだ1を得るかもしれません。


編集:論理右シフト(SHR)はa >> (b % 32/64)、x86 / x86-64(Intel#253667、ページ4-404)のように動作します。

デスティネーションオペランドは、レジスタまたはメモリ位置にすることができます。countオペランドは、即値またはCLレジスターにすることができます。カウントは5ビットにマスクされます(64ビットモードでREX.Wが使用されている場合は6ビット)。カウント範囲は0〜31(64ビットモードでREX.Wを使用する場合は63)に制限されます。特別なオペコードエンコーディングが1カウント用に提供されています。

ただし、ARM(少なくともarmv6&7)では、論理右シフト(LSR)は(ARMISAページA2-6)として実装されます。

(bits(N), bit) LSR_C(bits(N) x, integer shift)
    assert shift > 0;
    extended_x = ZeroExtend(x, shift+N);
    result = extended_x<shift+N-1:shift>;
    carry_out = extended_x<shift-1>;
    return (result, carry_out);

ここで(ARMISAページAppxB-13)

ZeroExtend(x,i) = Replicate('0', i-Len(x)) : x

これにより、32以上の右シフトでゼロが生成されることが保証されます。たとえば、このコードをiPhoneで実行するfoo(1,32)と、0になります。

これらは、32ビット整数を32以上シフトすることは移植性がないことを示しています。

于 2010-08-03T07:07:24.053 に答える
6

わかった。つまり、5.8.1にあります。

オペランドは整数型または列挙型であり、汎整数拡張が実行されます。結果のタイプは、プロモートされた左オペランドのタイプです。右のオペランドが負の場合、またはプロモートされた左のオペランドのビット単位の長さ以上の場合、動作は未定義です。

つまり、Undefined Behaviour(tm)があります。

于 2010-08-03T07:11:43.067 に答える
3

What happens in foo is that the shift width is greater than or equal to the size of the data being shifted. In the C99 standard that results in undefined behaviour. It's probably the same in whatever C++ standard MS VC++ is built to.

The reason for this is to allow compiler designers to take advantage of any CPU hardware support for shifts. For example, the i386 architecture has an instruction to shift a 32 bit word by a number of bits, but the number of bits is defined in a field in the instruction that is 5 bits wide. Most likely, your compiler is generating the instruction by taking your bit shift amount and masking it with 0x1F to get the bit shift in the instruction. This means that shifting by 32 is the same as shifting by 0.

于 2010-08-03T07:50:31.617 に答える
1

I compiled it on 32 bit windows using VC9 compiler. It gave me the following warning. Since sizeof(int) is 4 bytes on my system compiler is indicating that right shifting by 32 bits results in undefined behavior. Since it is undefined, you can not predict the result. Just for checking I right shifted with 31 bits and all the warnings disappeared and the result was also as expected (i.e. 0).

于 2010-08-03T07:13:30.443 に答える
0

その理由は、int型が32ビット(ほとんどのシステム)を保持しているためだと思いますが、符号付き型であるため、1ビットが符号に使用されます。したがって、実際の値には31ビットのみが使用されます。

于 2010-08-03T07:07:54.917 に答える
0

警告はそれをすべて言います!

しかし、公平を期すために、私は一度同じエラーに噛まれました。

int a = 1;
cout << ( a >> 32);

完全に未定義です。実際、私の経験では、コンパイラーは通常、ランタイムとは異なる結果をもたらします。これが意味するのは、コンパイラが実行時にシフト式を評価できる場合、実行時に評価された式とは異なる結果が得られる可能性があるということです。

于 2010-08-03T07:12:44.790 に答える
-5

foo(1,32) performs a rotate-shit, so bits that should disappear on the right reappear on the left. If you do it 32 times, the single bit set to 1 is back to its original position.

bar(1,32) is the same, but the bit is in the 64-32+1=33th bit, which is above the representable numbers for a 32-bit int. Only the 32 lowest bit are taken, and they are all 0's.

1 >> 32 is performed by the compiler. No idea why gcc uses a non-rotating shift here and not in the generated code.

Same thing for ((int)1 >> (int)32)

于 2010-08-03T07:14:29.507 に答える