32 비트 정수에 대해 왼쪽 비트 시프트 "<<"가 32 회 이상 사용될 때 예상대로 작동하지 않는 이유는 무엇입니까?
다음 프로그램을 작성하고 GNU C ++ 컴파일러를 사용할 때 출력은 1
컴파일러가 수행하는 회전 연산 때문이라고 생각합니다.
#include <iostream>
int main()
{
int a = 1;
std::cout << (a << 32) << std::endl;
return 0;
}
그러나 논리적으로 비트 폭을 초과하면 비트가 손실된다고하므로 출력은 0이어야합니다. 무슨 일이 일어나고 있습니까?
코드는 ideone, http://ideone.com/VPTwj에 있습니다.
이는 C에서 정의되지 않은 동작과 IA-32 프로세서 용으로 생성 된 코드에 시프트 카운트에 적용된 5 비트 마스크가 있다는 사실의 조합으로 인해 발생합니다. 이것은 IA-32 프로세서에서 시프트 카운트의 범위가 0-31 이라는 것을 의미합니다 . 1
에서 C 프로그래밍 언어 2
오른쪽 피연산자가 음수이거나 왼쪽 표현식 유형의 비트 수보다 크거나 같은 경우 결과는 정의되지 않습니다.
에서 IA-32 인텔 아키텍처 소프트웨어 개발자 설명서 3
8086은 시프트 카운트를 마스킹하지 않습니다. 그러나 다른 모든 IA-32 프로세서 (Intel 286 프로세서로 시작)는 시프트 카운트를 5 비트로 마스킹하므로 최대 31 개가됩니다.이 마스킹은 모든 작동 모드 (virtual-8086 모드 포함)에서 수행됩니다. 명령어의 최대 실행 시간을 줄입니다.
1 http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/
2 A7.8 시프트 연산자, 부록 A. 참조 설명서, C 프로그래밍 언어
3 SAL / SAR / SHL / SHR – Shift, 4 장. 명령어 세트 참조, IA-32 인텔 아키텍처 소프트웨어 개발자 매뉴얼
C ++에서 shift는 유형의 크기보다 작은 단계로 값을 이동하는 경우에만 잘 정의됩니다. 경우 int
32 비트 만에 0을 포함하고, 31 단계는 잘 정의된다.
그래서, 이것은 왜?
시프트를 수행하는 기본 하드웨어를 살펴보면 값의 하위 5 비트 (32 비트 경우) 만 확인해야한다면 검사해야하는 경우보다 더 적은 논리 게이트를 사용하여 구현할 수 있습니다. 가치의 모든 부분.
댓글의 질문에 대한 답변
C 및 C ++는 사용 가능한 모든 하드웨어에서 최대한 빠르게 실행되도록 설계되었습니다. 오늘날 생성 된 코드는 기본 하드웨어가 지정된 범위를 벗어난 값을 처리하는 방법에 관계없이 단순히``이동 ''명령입니다. 언어가 시프트 동작 방식을 지정했다면 생성 된 사용자는 시프트를 수행하기 전에 시프트 수가 범위 내에 있는지 확인해야 할 수 있습니다. 일반적으로 이것은 세 가지 명령 (비교, 분기, 시프트)을 생성합니다. (분명히,이 경우 시프트 카운트를 알기 때문에 필요하지 않습니다.)
C ++ 표준에 따라 정의되지 않은 동작입니다.
E1 << E2의 값은 E1 왼쪽으로 이동 한 E2 비트 위치입니다. 비워진 비트는 0으로 채워집니다. E1에 부호없는 유형이있는 경우 결과 값은 E1 × 2 ^ E2이며, 결과 유형에서 표현할 수있는 최대 값보다 모듈로 하나 더 축소됩니다. 그렇지 않으면 E1에 부호있는 유형과 음이 아닌 값이 있고 E1 × 2 ^ E2가 결과 유형에서 표현 될 수있는 경우 결과 값이됩니다. 그렇지 않으면 동작이 정의되지 않습니다 .
Lindydancer 및 6502의 답변은 (일부 컴퓨터에서) 1
인쇄되는 이유를 설명합니다 ( 작업 동작은 정의되지 않음). 명확하지 않은 경우를 대비하여 세부 정보를 추가하고 있습니다.
나처럼 인텔 프로세서에서 프로그램을 실행하고 있다고 가정합니다. GCC는 시프트 작업을 위해 다음과 같은 어셈블리 명령을 생성합니다.
movl $32, %ecx
sall %cl, %eax
Instruction Set Reference Manual의sall
624 페이지 및 기타 시프트 작업에 대한 내용은 다음 과 같습니다.
8086은 시프트 카운트를 마스킹하지 않습니다. 그러나 다른 모든 인텔 아키텍처 프로세서 (인텔 286 프로세서로 시작)는 시프트 카운트를 5 비트로 마스킹하므로 최대 31 개가됩니다.이 마스킹은 모든 작동 모드 (virtual-8086 모드 포함)에서 수행되어 명령어의 최대 실행 시간.
32의 하위 5 비트가 0이므로 1 << 32
는 1 << 0
, 즉 1
.
더 큰 숫자로 실험 해보면
cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n";
인쇄 1 2 4
하고 실제로 내 컴퓨터에서 일어나는 일입니다.
너무 많은 것을 기대하고 있기 때문에 예상대로 작동하지 않습니다.
x86의 경우 하드웨어는 카운터가 레지스터 크기보다 큰 시프트 연산에 대해 신경 쓰지 않습니다 (예를 들어 x86 참조 문서의 SHL 명령어 설명에서 설명 참조 ).
C ++ 표준은 생성 된 코드가 모든 매개 변수 이동에 대해 추가 검사와 논리를 추가해야했기 때문에 이러한 경우에 수행 할 작업을 지정하여 추가 비용을 부과하고 싶지 않았습니다.
이러한 자유를 통해 컴파일러 구현자는 테스트 나 분기없이 하나의 어셈블리 명령어 만 생성 할 수 있습니다.
예를 들어보다 "유용한"및 "논리적"접근 방식은 논리적이고 일관된 동작으로 높은 카운터를 처리 (x << y)
하는 것과 동등 (x >> -y)
하고 또한 처리하는 것입니다.
However this would have required a much slower handling for bit shifting so the choice was to do what the hardware does, leaving to the programmers the need to write their own functions for side cases.
Given that different hardware does different things in these cases what the standard says is basically "Whatever happens when you do strange things just don't blame C++, it's your fault" translated in legalese.
Shifting a 32 bit variable by 32 or more bits is undefined behavior and may cause the compiler to make daemons fly out of your nose.
Seriously, most of the time the output will be 0 (if int
is 32 bits or less) since you're shifting the 1 until it drops off again and nothing but 0 is left. But the compiler may optimize it to do whatever it likes.
See the excellent LLVM blog entry What Every C Programmer Should Know About Undefined Behavior, a must-read for every C developer.
Since you are bit shifting an int by 32 bits; you'll get: warning C4293: '<<' : shift count negative or too big, undefined behavior
in VS. This means that you're shifting beyond the integer and the answer could be ANYTHING, because it is undefined behavior.
You could try the following. This actually gives the output as 0
after 32
left shifts.
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int a = 1;
a <<= 31;
cout << (a <<= 1);
return 0;
}
I had the same problem and this worked for me:
f = ((long long)1 << (i-1));
Where i can be any integer bigger than 32 bits. The 1 has to be a 64 bit integer for the shifting to work.
'IT Share you' 카테고리의 다른 글
가로 스크롤이있는 HTML 표 (첫 번째 열 고정) (0) | 2020.12.10 |
---|---|
Rails를 사용하여 이메일에 이미지를 삽입하는 올바른 방법은 무엇입니까? (0) | 2020.12.10 |
if 조건과 중괄호가없는 변수를 선언 할 때 컴파일러 오류 (0) | 2020.12.10 |
Jenkins에서 프로젝트를 특정 디렉터리로 체크 아웃하는 방법 (GIT 사용) (0) | 2020.12.10 |
파이썬 문자열에서 쉼표를 제거하는 방법 (0) | 2020.12.10 |