24.3 부호 있는 자료형의 비트 연산 알아보기

지금까지 부호 없는(unsigned) 자료형으로 비트 연산을 했습니다. 하지만 부호 있는 자료형을 비트 연산할 때는 부호 비트를 조심해야 합니다.

먼저 부호 없는 자료형과 부호 있는 자료형에 >> 연산을해보겠습니다.

bitwise_right_shift_signed_unsigned.c

#include <stdio.h>
 
int main()
{
    unsigned char num1 = 131;    //  131: 1000 0011
    char num2 = -125;            // -125: 1000 0011
 
    unsigned char num3;
    char num4;
 
    num3 = num1 >> 5;    // num1의 비트 값을 오른쪽으로 5번 이동
    num4 = num2 >> 5;    // num2의 비트 값을 오른쪽으로 5번 이동
 
    printf("%u\n", num3);    //  4: 0000 0100: 맨 뒤의 11은 사라지고 0000 0100이 됨
    printf("%d\n", num4);    // -4: 1111 1100: 모자라는 공간은 부호 비트의 값인 1로  
                             // 채워지므로 1111 1100이 됨
 
    return 0;
}

실행 결과

4
-4

1바이트짜리 부호 없는 변수에 131과 부호 있는 변수에 -125를 할당했습니다. 부호 없는 변수의 1000 0011(131)을 오른쪽으로 5번 이동시켰을 때는 0000 0100(4)가 나왔는데 부호 있는 변수의 1000 0011(-125)를 오른쪽으로 5번 이동시키니 1111 1100(-4)가 나왔습니다. 왜 이렇게 다른 값이 나올까요?

1000 0011(131)
_________ >> 5
0000 0100(4)
1000 0011(-125)
_________ >> 5
1111 1100(-4)

부호 있는 자료형의 첫 번째 비트는 부호 비트라고 하는데 이 비트가 1이면 음수, 0이면 양수입니다.

그림 24‑2 부호 있는 자료형의 부호 비트

부호 있는 자료형에 저장된 1000 0011은 첫 번째 비트가 1이므로 음수이고 10진수로는 -125가 됩니다. 이 비트들을 오른쪽으로 5번 이동시키면 모자라는 공간은 모두 부호 비트의 값으로 채워지기 때문에 1111 1100(-4)가 됩니다. 하지만 부호 없는 자료형은 비트를 오른쪽으로 이동해도 모자라는 공간은 모두 0으로 채워집니다. 즉, 비트 연산자는 부호 있는 자료형과 부호 없는 자료형이 다르게 동작합니다.

그림 24‑3 부호 없는 자료형과 부호 있는 자료형의 비트 이동

그러면 부호 있는 자료형에서 부호 비트가 0인 양수에 >> 연산을 하면 어떻게 될까요?

bitwise_right_shift_signed.c

#include <stdio.h>
 
int main()
{
    char num1 = 67;    // 67: 0100 0011
    char num2;
 
    num2 = num1 >> 5;    // num1의 비트 값을 오른쪽으로 5번 이동
 
    printf("%d\n", num2);    // 2: 0000 0010: 모자라는 공간은 부호 비트의 값인 0으로 채워지므로 
                             // 0000 0010이 됨
 
    return 0;
}

실행 결과

2

부호 있는 자료형에 저장된 0100 0011(67)은 부호 비트가 0입니다. 따라서 오른쪽으로 5번 이동했을 때 모자라는 공간은 부호 비트의 값인 0으로 채워지므로 0000 0010(2)이 됩니다.

그림 24‑4 부호 있는 자료형에서 부호 비트가 0일 때 비트 이동

이번에는 부호 있는 자료형에서 << 연산을 해보겠습니다.

bitwise_left_shift_signed.c

#include <stdio.h>
 
int main()
{
    char num1 = 113;    //  113: 0111 0001
    char num2 = -15;    //  -15: 1111 0001
    char num3, num4, num5, num6;
 
    num3 = num1 << 2;    // num1의 비트 값을 왼쪽으로 2번 이동
    num4 = num2 << 2;    // num2의 비트 값을 왼쪽으로 2번 이동
 
    num5 = num1 << 4;    // num1의 비트 값을 왼쪽으로 4번 이동
    num6 = num2 << 4;    // num1의 비트 값을 왼쪽으로 4번 이동
 
    printf("%d\n", num3);    // -60: 1100 0100: 부호 비트를 덮어쓰게 되므로 양수에서 음수가 됨
    printf("%d\n", num4);    // -60: 1100 0100: 이미 음수인 수는 계속 음수가 됨
 
    printf("%d\n", num5);    // 16: 0001 0000: 이미 양수인 수는 계속 양수가 됨
    printf("%d\n", num6);    // 16: 0001 0000: 부호 비트를 덮어쓰게 되므로 음수에서 양수가 됨
 
    return 0;
}

실행 결과

-60
-60
16
16

부호 있는 자료형에서 첫 번째 비트가 0인 양수 0111 0001(113)을 왼쪽으로 2번 이동시키면 1이 부호 비트를 덮어쓰게 됩니다(오버플로우 상황). 따라서 1100 0100(-60)이 되고, 양수였던 수가 음수가 되어버립니다.

그림 24‑5 부호 비트가 0에서 1로 바뀌는 상황

이미 첫 번째 비트가 1인 음수 1111 0001(-15)는 왼쪽으로 2번 이동시켜서 부호 비트를 덮어쓰더라도 부호 비트는 바뀌지 않으므로 계속 음수 1100 0100(-60)이 됩니다.

그림 24‑6 부호 비트가 1로 유지되는 상황

비트를 왼쪽으로 좀 더 이동시켜서 0이 부호 비트를 덮어쓰게 만들면 음수였던 수는 양수가 됩니다. 즉, 음수인 1111 0001(-15)를 왼쪽으로 4번 이동시키면 1은 모두 사라지고, 부호 비트에 0이 와서 양수 0001 0000(16)이 됩니다.

그림 24‑7 부호 비트가 0으로 바뀌는 상황

이미 첫 번째 비트가 0인 양수 0111 0001(113)을 왼쪽으로 4번 이동시키면 앞의 1은 모두 사라지고, 부호 비트에 0이 오므로 계속 양수 0001 0000(16)이 됩니다.

그림 24‑8 부호 비트가 바뀌지 않는 상황

부호 있는 자료형에서 비트를 왼쪽으로 이동시켰을 때는 부호 비트에 위치한 숫자에 따라 양수, 음수가 결정됩니다. 따라서 부호 있는 자료형에 시프트 연산을 할 때는 의도치 않은 결과가 나올 수 있으므로 항상 부호 비트를 생각해야 합니다.