1. << 연산자 (산술적 왼쪽 시프트, Arithmetic Left Shift)
왼쪽으로 비트를 이동시키고, 이동 후 오른쪽에 생긴 빈 공간은 0 으로 채운다.
비트를 한 번 이동시킬 때마다 값에 2가 곱해진다.
- 예 :
1 << 3→ $2^3 = 8$
2. >> 연산자 (산술적 오른쪽 시프트, Arithmetic Right Shift)
오른쪽으로 비트를 이동시키고, 이동 후 왼쪽에 생긴 빈 공간은 양수의 경우 0 으로, 음수의 경우 1 로 채운다.
즉, 부호 비트를 유지하면서 오른쪽으로 비트를 이동시킨다.
비트를 한 번 이동시킬 때마다 값이 2로 나눠진다.
- 예 :
-8 >> 2→ $-8 \div 4 = -2$
3. >>> 연산자 (논리적 오른쪽 시프트, Logical Right Shift)
C/C++에 존재하지 않는 연산자이지만, 여기서 같이 정리하면 좋을 것 같아서 가져왔다.
Java에서는 >>> 연산자가 존재한다. Java에서 >> 연산자와 >>> 연산자는 둘 다 오른쪽 시프트를 수행하지만, 부호 비트 처리 방식에서 차이가 있다.
오른쪽으로 비트를 이동시키고, 이동 후 왼쪽에 생긴 빈 공간은 0 으로 채운다.
즉, 부호 비트를 무시한 채 오른쪽으로 비트를 이동시키고 왼쪽에 항상 0 을 채운다.
부호를 무시하고 순수한 비트를 처리해야 할 때 사용한다. (예를 들어 파일, 네트워크 데이터 패킷, 또는 unsigned 데이터의 처리를 다뤄야 할 때)
4. 문제 : 1 << 30 , 1 << 31 , 1 << 32 , 1 << 33 의 결과는?
먼저 1 << 30 의 경우, 비트로 표현하면 01000000 00000000 00000000 00000000 이다.
따라서 (signed int 기준으로) 값은 1073741824 이다.
다음으로 1 << 31 의 경우, 비트로 표현하면 10000000 00000000 00000000 00000000 이다.
따라서 (signed int 기준으로) 값은 -2147483648 이다. (부호 비트가 1 이므로 음수가 된다.)
마지막으로 1 << 32 , 1 << 33 의 경우이다.
C/C++에서 일반적으로 부호 있는 정수 자료형의 비트 개수 이상으로 시프트를 하거나, 음수 시프트를 하는 경우는 UB(Undefined Behavior)이다.
즉, 1 << 32 와 1 << 33 은 UB이다.
정보를 찾다보니, C/C++ 버전에 따라 시프트했을 때 부호 비트를 침범하는 경우에도 UB인 경우가 있다고 한다.
이렇게 설계된 이유는 C가 만들어질 때 CPU의 shift instruction이 각자 달랐던 것 때문으로 추정된다고 한다.
또한 시프트하는 범위를 굳이 체크하지 않음으로써 프로그램의 성능을 유지할 수 있다고 한다.
5. 문제 : (1 << 30) >> 30 , (1 << 31) >> 31 의 결과는?
먼저, (1 << 30) >> 30 의 결과는 1 이다.
1 << 30:01000000 00000000 00000000 00000000=1073741824(1 << 30) >> 30:00000000 00000000 00000000 00000001=1
다음으로, (1 << 31) >> 31 의 결과는 -1 이다.
1 << 31:10000000 00000000 00000000 00000000=-2147483648(1 << 31) >> 31:11111111 11111111 11111111 11111111=-1- (음수에 대해 오른쪽 시프트 연산을 수행하면 왼쪽이
1로 채워지기 때문이다.)
- (음수에 대해 오른쪽 시프트 연산을 수행하면 왼쪽이
6. 안전한 시프트 방법
초과 시프트나 음수 시프트는 UB를 발생시킬 수 있다. 또한 컴파일러마다 다르게 동작할 수 있다.
따라서 비트 시프트는 자료형(signed/unsigned)과 시프트 횟수의 범위를 항상 고려해야 한다.
초과 시프트의 경우에는 나머지 연산( % )으로 처리하거나 구현에 따라 값을 반환하여 초과 시프트를 방지해야 한다.
7. 참고 자료
(2024.12.23 월 작성)
(2025.02.12 수 수정 및 업로드)