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 부호 비트가 바뀌지 않는 상황

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