Skip to content

18. 비트 연산자


1. 키워드

  • 비트 연산자
  • 플래그(Flag)


2. 비트 연산자 사용하기

  • 지금까지 자료형을 바이트 단위로 구분하여 사용했다.
  • 비트 연산자는 바이트 단위보다 더 작은 비트 단위로 연산하는 연산자이다.


1] 비트(Bit)

  • 2진수를 저장하는 단위이다.
  • 컴퓨터에서 사용할 수 있는 최소 단위이며 01을 나타낸다.

2] 바이트(Byte)

  • 8비트 크기의 단위이다.


  • 다음은 비트 연산자의 종류이다.


001


  • 비트 연산자는 비트로 옵션을 설정할 때 주로 사용하며 저장 공간을 아낄 수 있는 장점이 있다.
  • 특히 이런 방식을 플래그라고 부른다.


3. 비트 AND, OR, XOR 연산자 사용하기

  • 이제 비트 연산자를 사용하여 값을 계산해 보자.


#include <stdio.h>

int main()
{
    unsigned char num1 = 1; // 0000 0001
    unsigned char num2 = 3; // 0000 0011

    printf("%d\n", num1 & num2); // 0000 0001: 01과 11을 비트 AND하면 01이 됨
    printf("%d\n", num1 | num2); // 0000 0011: 01과 11을 비트 OR하면 11이 됨
    printf("%d\n", num1 ^ num2); // 0000 0010: 01과 11을 비트 XOR하면 10이 됨

    return 0;
}

// 1
// 3
// 2


  • 각 줄에 해당 값의 10진수와 2진수(비트)를 적어놓았다.
  • unsigned char 타입에 1을 할당했을 때 비트로 표현하면 0000 0001이다.
  • 마찬가지로 30000 0011이다.
  • 비트 연산은 두 값을 비트 단위로 나열한 뒤 각 자릿수를 비트 연산자로 연산한다.
  • 각 자릿수의 연산은 독립적이며 다른 자릿수에 영향을 주지 않는다.
  • 그리고 비트 단위로 연산한 각 자릿수를 모두 모으면 최종 결과가 된다.


  • & 연산자는 비트 AND이므로 두 비트가 모두 1일 때 1이다.
  • 따라서 하나라도 0이면 0이 나온다.
  • 즉, 0000 00010000 0011을 비트 AND 연산했을 때 0 & 10 그리고 1 & 11이 나오므로 0000 0001이 된다.
  • 10진수로 표현하면 1 & 31이 된다.


002


  • 다음은 120 & 26 연산이다.


0111 1000(120)
0001 1010(26)
_________ &
0001 1000(24)


  • 0001 1000이 나오므로 10진수로 24가 된다.


  • | 연산자는 비트 OR이므로 두 비트 중 하나만 1이라도 1이다.
  • 따라서 두 비트가 모두 0일 때만 0이다.
  • 즉, 0000 00010000 0011을 비트 OR 연산했을 때 0 | 11 그리고 1 | 11이 나오므로 0000 0011이 된다.
  • 10진수로 표현하면 1 | 33이다.


0000 0001(1)
0000 0011(3)
_________ |
0000 0011(3)


  • ^ 연산자는 비트 XOR이므로 두 비트가 다를 때 1이다.
  • 따라서 11일 때, 00일 때는 모두 0이다.
  • 즉, 0000 00010000 0011을 비트 XOR 연산했을 때 0 ^ 11 그리고 1 ^ 10이 나오므로 0000 0010이 된다.
  • 10진수로 표현하면 1 ^ 32이다.


0000 0001(1)
0000 0011(3)
_________ ^
0000 0010(2)


4. 비트 NOT 연산자 사용하기

  • 이번에는 ~ 연산자를 사용해 보자.


#include <stdio.h>

int main()
{
    unsigned char num1 = 162; // 162: 1010 0010
    unsigned char num2;

    num2 = ~num1;

    printf("%u\n", num2); // 0101 1101: num1의 비트 값을 뒤집음

    return 0;
}

// 93


  • ~ 연산자는 비트 NOT 연산자이다.
  • 간단하게 0110으로 바꾸며 "비트를 뒤집는다" 또는 "비트 반전"이라고 말한다.
  • 1010 0010의 각 비트를 뒤집으면 0101 1101이 되고, 10진수로 93이다.
  • 즉, ~16293이다.


1010 0010(162)
_________ ~
0101 1101(93)


unsigned char 자료형을 사용하는 이유는?

  • unsigned char는 부호 없는 정수이며 1바이트 크기이다.
  • 비트 연산으로 인해 부호 비트가 영향을 받지 않도록 부호 없는 자료형을 사용했다.


5. 시프트 연산자 사용하기

  • C에서 비트의 논리 연산뿐만 아니라 각 자리를 이동시킬 수도 있다.
  • 이번에는 비트의 자리를 이동시켜 보자.


#include <stdio.h>

int main()
{
    unsigned char num1 = 3;  //  3: 0000 0011
    unsigned char num2 = 24; // 24: 0001 1000

    printf("%u\n", num1 << 3); // 0001 1000: num1의 비트 값을 왼쪽으로 3번 이동
    printf("%u\n", num2 >> 2); // 0000 0110: num2의 비트 값을 오른쪽으로 2번 이동

    return 0;
}

// 24
// 6


  • 시프트 연산자를 사용하여 각 변수의 비트를 지정한 횟수대로 이동했다.


  • 다음은 시프트 연산을 하는 방법이며 << 연산자를 예로 들었다.


003


  • 시프트 연산은 변수 << 이동할 비트 수 또는 변수 >> 이동할 비트 수 형식으로 사용한다.
  • 즉, 지정한 횟수대로 비트를 이동시키며 모자라는 공간은 0으로 채운다.
  • 연산자 모양 그대로 <<는 왼쪽, >>는 오른쪽 방향이다.


  • num1 << 30000 0011을 왼쪽으로 3번 이동하므로 0001 1000이 되고, 10진수로 24이다.


0000 0011(3)
_________ << 3
0001 1000(24)


  • num2 >> 20001 1000이 오른쪽으로 2번 이동하므로 0000 0110이 되고, 10진수로 6이다.


0001 1000(24)
_________ >> 2
0000 0110(6)


  • 3 << 3\(3 * 2^3\)과 같으므로 24가 되고, 24 >> 2\(24 / 2^2\)과 같으므로 6이 된다.
  • 즉, 시프트 연산 <<은 2의 거듭제곱을 곱하기, >>은 2의 거듭제곱을 나누기이다.


6. 비트 연산 후 할당하기

  • 이번에는 비트 연산자와 할당 연산자를 함께 사용해 보자.


#include <stdio.h>

int main()
{
    unsigned char num1 = 4; // 4: 0000 0100
    unsigned char num2 = 4; // 4: 0000 0100
    unsigned char num3 = 4; // 4: 0000 0100
    unsigned char num4 = 4; // 4: 0000 0100
    unsigned char num5 = 4; // 4: 0000 0100

    num1 &= 5;  // 5(0000 0101) AND 연산 후 할당
    num2 |= 2;  // 2(0000 0010) OR 연산 후 할당
    num3 ^= 3;  // 3(0000 0011) XOR 연산 후 할당
    num4 <<= 2; // 비트를 왼쪽으로 2번 이동한 후 할당
    num5 >>= 2; // 비트를 오른쪽으로 2번 이동한 후 할당

    printf("%u\n", num1); // 0000 0100: 100과 101을 비트 AND하면 100이 됨
    printf("%u\n", num2); // 0000 0110: 100과 010을 비트 OR하면 110이 됨
    printf("%u\n", num3); // 0000 0111: 100과 011을 비트 XOR하면 111이 됨
    printf("%u\n", num4); // 0001 0000: 100을 왼쪽으로 2번 이동하면 10000이 됨
    printf("%u\n", num5); // 0000 0001: 100을 오른쪽으로 2번 이동하면 1이 됨

    return 0;
}

// 4
// 6
// 7
// 16
// 1


  • 각 자리별로 10진수와 2진수를 적어놓았다.
  • &=는 다른 값과 비트 AND 연산을 한 뒤 다시 자기 자신에게 할당한다는 뜻이다.
  • 다른 연산자도 마찬가지로 해당 연산을 수행한 뒤 다시 자기 자신에게 할당한다.
  • 여기서는 각 변수에 4가 들어있다.


  • num1 &= 50000 01000000 0101을 비트 AND 연산했을 때 1 & 11, 0 & 00, 0 & 10이 나오고 이 값을 다시 할당해서 0000 0100이 된다.


0000 0100(4)
0000 0101(5)
_________ &
0000 0100(4)


  • num2 |= 20000 01000000 0010을 비트 OR 연산했을 때 1 | 01, 0 | 11, 0 | 00이 나오고 이 값을 다시 할당해서 0000 0110이 된다.


0000 0100(4)
0000 0010(2)
_________ |
0000 0110(6)


  • num3 ^= 30000 01000000 0011을 비트 XOR 연산했을 때 1 ^ 01, 0 ^ 11, 0 ^ 11이 나오고 이 값을 다시 할당해서 0000 0111이 된다.


0000 0100(4)
0000 0011(3)
_________ ^
0000 0111(7)


  • num4 <<= 20000 0100을 왼쪽으로 2번 이동한 뒤 다시 할당하여 0001 0000이 된다.


0000 0100(4)
_________ << 2
0001 0000(16)


  • num5 >>= 20000 0100을 오른쪽으로 2번 이동한 뒤 다시 할당하여 0000 0001이 된다.


0000 0100(4)
_________ >> 2
0000 0001(1)


  • 즉, 기존에 들어있던 값은 사라지고 새로 연산한 값이 할당된다.


  • 이 연산을 풀어보면 다음과 같다.


num1 = num1 & 5; // 5(0000 0101) AND 연산 후 할당
num2 = num2 | 2; // 2(0000 0010) OR 연산 후 할당
num3 = num3 ^ 3; // 3(0000 0011) XOR 연산 후 할당
num4 = num4 << 2; // 비트를 왼쪽으로 2번 이동한 후 할당
num5 = num5 >> 2; // 비트를 오른쪽으로 2번 이동한 후 할당


  • 출력 결과는 앞의 연산 결과와 같다.
  • 즉, &=, |=, ^=, <<=, >>= 연산자는 반복되는 변수 부분을 생략하기 위해 사용한다.
  • 특히, 비트 연산에서 연산과 할당이 한꺼번에 이루어지는 연산자는 플래그를 켜거나 끌 때 유용하게 활용된다.

References