12 비트 연산자와 시프트 연산자로 비트 단위 연산하기

  • 25 minutes to read

이번 강의는 변수의 값을 비트 단위로 연산하는 비트 연산자와 시프트 연산자의 사용법에 대해 다룹니다.

> // 비트 연산자: & (비트단위 AND), | (비트단위 OR), ^ (비트단위 XOR), ~ (비트단위 NOT)
> // 시프트 연산자: << (왼쪽 시프트), >> (오른쪽 시프트)

12.1 비트 연산자와 시프트 연산자의 종류

비트 연산자와 시프트 연산자는 2진수 비트 단위로 연산을 수행하는데 사용합니다. 우선 비트 연산자와 시프트 연산자의 6가지 종류를 표로 간단히 미리 살펴본 후 진행하겠습니다.

[표] 비트 연산자와 시프트 연산자의 종류

연산자 별칭 설명
& 비트단위 AND 비트 값이 둘 다 1일 때에만 1, 그렇지 않으면 0
| 비트단위 OR 비트 값이 하나라도 1이면 1, 그렇지 않으면 0
^ 비트단위 XOR 비트 값이 서로 다를 때에만 1, 그렇지 않으면 0
~ 비트단위 NOT 비트 값이 1이면 0, 0이면 1
<< 왼쪽 시프트 비트 값을 지정된 수만큼 왼쪽으로 이동
>> 오른쪽 시프트 비트 값을 지정된 수만큼 오른쪽으로 이동

12.2 비트 연산자

비트 연산자(Bit Operator)는 정수형 데이터의 값을 이진수의 비트 단위로 연산을 수행하고자할 때 사용됩니다. 비트 연산자는 다음과 같이 4개의 기호를 사용합니다. 좀 더 구분을 짓기 위해서 영문 단어를 추가했습니다.

  • &
    • 논리곱(AND, Both)
  • |
    • 논리합(OR, Either)
  • ^
    • 배타적 논리합(XOR; Exclusive OR, Different)
  • !
    • NOT(Negation, Invert)

12.2.1 비트 연산자 AND, OR, XOR 간단 소개 동영상

https://youtu.be/Z19Iv9m0PLc

12.3 Windows 계산기를 사용하여 이진수 비트 연산자 4개에 대한 실행 결과 살펴보기

https://youtu.be/aYpD6cHqpsk

Windows OS에 내장된 계산기를 사용하면 표준, 공학용, 프로그래머용 등의 여러 가지 환경을 사용할 수 있습니다.

그림: 계산기

계산기

12.4 비트 AND 연산자: & 연산자

본격적으로 비트 연산자를 사용하도록 하겠습니다. 먼저, 비트 AND 연산자인 & 연산자를 사용해 보겠습니다.

그림: 계산기로 AND 연산 수행하기

계산기로 AND 연산 수행하기

코드: bit_and.c

// & 연산자: 비트 AND 연산자(둘 다 1일때만 1)
#include <stdio.h>

int main(void)
{
   int x = 0b1010; // 10진수: 10
   int y = 0b1100; // 10진수: 12

   // x를 10진수로 표현
   printf("%d\n", x); // 10

   // y를 10진수로 표현
   printf("%d\n", y); // 12

   // x AND y를 10진수로 표현 
   printf("%d\n", x & y); // 8 

   return 0;
}
10
12
8

비트 AND 연산자인 & 연산자처럼 항을 2개 갖는 2항 연산자는 앞뒤로 공백을 두면 가독성이 좋아집니다. 즉, x%y 보다는 x % y 형태를 추천합니다.

12.5 비트 OR 연산자: | 연산자

이번에는 비트 OR 연산자인 | 연산자를 사용해 보겠습니다. | 연산자는 키보드의 파이프 기호(버티컬 바)를 사용합니다.

그림: 계산기로 OR 연산 수행하기

계산기로 OR 연산 수행하기

코드: bit_or.c

// | 연산자: 비트 OR 연산자(하나라도 1이면 1)
#include <stdio.h>

int main(void)
{
   int x = 0b1010; // 10진수: 10
   int y = 0b1100; // 10진수: 12

   // x를 이진수에서 십진수로 출력
   printf("%d\n", x); // 10

   // y를 이진수에서 십진수로 출력
   printf("%d\n", y); // 12

   printf("--\n");

   // x OR y를 십진수로 표현 
   printf("%d\n", x | y); // 14

   return 0;
}
10
12
--
14

나중에 프로그래머로 일을 하다보면 이진수 처리가 중요한 분야가 있을 수 있습니다. 하지만, 처음 학습자에게는 "이런 연산자를 사용하면 이렇게 나오는구나" 정도로 알고 넘어갑니다.

12.6 비트 XOR 연산자: ^ 연산자

비트 XOR 연산자인 ^ 연산자는 두 비트의 값이 서로 다를 때 1입니다. 1 ^ 00 ^ 1일 때에만 1이고 나머지는 0입니다.

그림: 계산기로 XOR 연산 수행하기

계산기로 XOR 연산 수행하기

코드: bit_xor.c

// ^ 연산자: 비트 XOR 연산자(서로 다르면 1)
#include <stdio.h>

int main(void)
{
   int x = 0b1010; // 10진수: 10
   int y = 0b1100; // 10진수: 12

   // x를 십진수로 표현
   printf("%d\n", x); // 10

   // y를 십진수로 표현
   printf("%d\n", y); // 12

   printf("--\n");

   // x XOR y를 십진수로 표현 
   printf("%2d\n", x ^ y); // 6

   return 0;
}
10
12
--
 6

^ 연산자를 포함한 몇몇 비트 및 시프트 연산자는 이번 예제 이외에는 다루지 않습니다. 그러므로 현재 예제는 가볍게 작성 후 실행해보세요.

12.6.1 비트 NOT 연산자: ~ 연산자

비트 NOT 연산자인 ~ 연산자는 물결(틸드) 기호를 사용합니다. ~ 연산자는 비트 값이 1이면 0으로 0이면 1로 바꿉니다. 이렇게 비트가 바뀌는 것이 비트 반전입니다.

그림: 계산기로 비트 반전

계산기로 NOT 연산 수행하기

코드: bit_not.c

// ~ 연산자: 비트 NOT 연산자(1 <-> 0, 비트 반전)
#include <stdio.h>

int main(void)
{
   int x = 0b00001010; // 10진수: 10

   // x를 십진수로 표현
   printf("~%d\n", x); // 10

   // NOT x를 십진수로 표현 
   printf("%3d\n", ~x); // -11

   return 0;
}
~10
-11

음수를 이진수로 표현하는 것은 수학의 2의 보수법을 활용합니다. C 언어는 알아서 이진수의 값을 양수 또는 정수로 출력해 줍니다.

12.7 비트 연산자 4가지 모두 사용해보기

이번에는 비트 연산자 네가지를 모아서 사용해보겠습니다.

코드: bitwise_operator.c

// 비트 연산자: &, |, ~, ^
#include <stdio.h>

int main(void)
{
   int x = 0b1010; // 10진수: 10
   int y = 0b0110; // 10진수: 6

   int _and = x & y; // 0010 => 2
   printf("%d\n", _and); // 2

   int _or = x | y; // 1110 => 14
   printf("%d\n", _or); // 14

   int _xor = x ^ y; // 1100 => 12
   printf("%d\n", _xor); // 12

   // 2의 보수법에 의해서 1010+1 그리고 부호를 -로 -1011 => -11
   int _not = ~x; // ~~~~0101 => -11
   printf("%d\n", _not); // -11

   return 0;
}
2
14
12
-11

이진수 10100110에 대해서 &, |, ^ 연산자를 사용한 결과와 1010~연산자를 붙여 비트를 반전시키는 연산의 사용 결과가 나옵니다.

12.8 시프트 연산자

시프트 연산자(Shift Operator)는 정수 데이터가 담겨 있는 메모리의 비트를 왼쪽(<<) 또는 오른쪽(>>)으로 지정한 비트만큼 이동시켜주는 기능을 제공합니다. 시프트 연산자를 사용하면 비트의 자리를 다음과 같이 이동할 수 있습니다.

  • x << y : xy만큼 왼쪽으로 이동
  • x >> y : xy만큼 오른쪽으로 이동

예를 들어, 정수형 데이터인 2를 이진수로 표현하면 0010입니다. 왼쪽(<<) 시프트 연산자를 사용하여 한 칸 이동하면 0100이 됩니다. 이진수 0010을 오른쪽(>>) 시프트 연산자를 사용하여 한 칸 이동하면 0001이 됩니다.

비트 연산자의 사용 예를 표로 정리하면 다음과 같습니다.

연산자 사용 예 설명
<< 변수 << 비트값; 비트값 만큼 왼쪽으로 비트 이동
결괏값이 변수의 값 * 2의 비트값 제곱 == 비트당 2배
변수의 값 곱하기 2의 거듭제곱
>> 변수 >> 비트값; 비트값 만큼 오른쪽으로 비트 이동
결괏값이 변수의 값 / 2의 비트값 제곱 == 비트당 1/2배
변수의 값 나누기 2의 거듭제곱

시프트 연산자에 대한 내용을 그림으로 표현하면 다음과 같습니다.

그림: 시프트 연산자

시프트 연산자

정수 2의 이진수인 0010을 왼쪽으로 2칸 이동하면 1000이 되어 정수 8이 됩니다.

정수 32의 이진수인 0101000를 오른쪽으로 2칸 이동하면 0001010이 되어 정수 10이 됩니다.

12.8.1 시프트 연산자 사용하기

코드로 시프트 연산자를 사용해 보도록 하겠습니다.

코드: shift_operator.c

// shift_operator.c
#include <stdio.h>

int main(void)
{
   int num = 2; // 0010 

   int left = num << 1; // 0010 -> 0100: 4
   int right = num >> 1; // 0010 -> 0001: 1

   printf("%d\n", left); // 4
   printf("%d\n", right); // 1

   return 0;
}
4
1

이진수 0010을 왼쪽으로 비트를 한 칸 이동하면 0100이 되고 이진수 0010을 오른쪽으로 비트를 한 칸 이동하면 0001이 됩니다. 시프트 연산자는 내부적으로는 이진수로 계산이 되지만 정수형 데이터이기에 출력할 때에는 그대로 십진수로 표현됩니다.

12.8.2 비트 연산자와 시프트 연산자를 할당 연산자와 함께 사용하기

비트 연산자와 시프트 연산자도 할당 연산자와 함께 사용할 수 있습니다. 사용 방법은 일반적인 할당 연산자와 동일합니다.

  • A &= B;
  • A |= B;
  • A ^= B;
  • A <<= B;
  • A >>= B;

이번에는 할당 연산자와 함께 비트 연산자, 시프트 연산자를 사용해 보겠습니다.

코드: bitwise_assignment.c

// bitwise_assignment.c
#include <stdio.h>

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

   num1 &= 5;  // 4(0100) & 5(0101) => 4(0100)
   num2 |= 1;  // 4(0100) | 1(0001) => 5(0101)
   num3 ^= 2;  // 4(0100) ^ 2(0010) => 6(0110)
   num4 <<= 1; // 4(0100) << 1 => 8(1000)
   num5 >>= 1; // 4(0100) >> 1 => 2(0010)

   printf("%d\n", num1); // 4
   printf("%d\n", num2); // 5
   printf("%d\n", num3); // 6
   printf("%d\n", num4); // 8
   printf("%d\n", num5); // 2

   return 0;
}
4
5
6
8
2

결과는 2진수 비트 연산 후 그 결괏값을 다시 변수에 할당한 내용이 출력됩니다.

비트 연산자와 시프트 연산자는 C 언어와 같은 프로그래밍 언어로 성능이 낮은 장치에 대한 프로그래밍할 때 많이 사용됩니다. 다만, C 언어에서 응용 프로그램을 제작할 때에는 자주 사용되지 않습니다. 이번 장의 예제의 내용 정도만 이해하고 다음으로 넘어가도 충분합니다.

12.9 기타 연산자 사용하기 및 연산자 우선순위 알아보기

이번 강의는 3개의 항을 다루는 3항 연산자 및 나머지 연산자를 정리하고 연산자들의 우선순위를 정리하겠습니다.

> // 조건 연산자(3항 연산자): "조건식 ? 조건식이참일때의값 : 조건식이거짓일때의값"

12.10 조건 연산자

"? :" 형태의 모양을 갖는 조건 연산자(Conditional Operator)도 있습니다. 조건연산자는 조건에 따라서 참(true)일 때와 거짓(false)일 때의 결과를 다르게 반환하고자 할 때 사용됩니다. 조건 연산자는 항이 3개여서 3항 연산자(Ternary Operator)라고도 합니다. 3항 연산자는 뒤에서 다룰 if~else문의 축약형이기도 합니다.

> (5 > 3) ? "TRUE" : "FALSE"
"TRUE"

3항 연산자인 ?: 연산자는 항이 3개가 있는 연산자로 다음과 같이 조건을 처리합니다.

  • 조건식 ? 식1 : 식2;

조건식이 참이면 식1이 실행되고 조건식이 거짓이면 식2가 실행됩니다. 3항 연산자의 결괏값이 특정 값을 반환하기에 다음과 같이 표현하기도 합니다.

  • 조건식 ? 값1 : 값2;

?: operator 보충 설명

다음 예제와 같이, 3개로 구성된 조건 연산자라고도 하는 조건 연산자 ?:은 부울 식을 계산하고 부울 식이 true 또는 false로 계산되는지에 따라 두 식 중 하나의 결과를 반환합니다.

(tempInCelsius < 20.0) ? "Cold." : "Perfect!";

위의 예제에서 볼 수 있듯이 조건 연산자의 구문은 다음과 같습니다.

condition ? consequent : alternative

condition 식은 true 또는 false로 계산되어야 합니다. conditiontrue로 계산되면 consequent 식이 계산되고 해당 결과가 연산 결과가 됩니다. conditionfalse로 계산되면 alternative 식이 계산되고 해당 결과가 연산 결과가 됩니다. consequent 또는 alternative만 계산됩니다.

조건부 연산자는 오른쪽 결합성입니다. 즉, 다음 형식의 식을 가정해 보세요.

a ? b : c ? d : e

이 식은 다음과 같이 계산됩니다.

a ? b : (c ? d : e)
TIP

다음과 같은 니모닉 디바이스(mnemonic device)를 사용하여 조건부 연산자의 평가 방식을 기억할 수 있습니다.

is this condition true ? yes : no

12.11 조건 연산자 사용하기

3항 연산자인 조건 연산자를 사용해보겠습니다.

코드: conditional_operator.c

// conditional_operator.c
//[?] 3항 연산자(조건 연산자) 사용하기 
#include <stdio.h>

int main(void)
{
   int num = 3;

   // num 변수가 짝수면 result 변수에 "짝수" 담고 그렇지 않으면 "홀수" 담기 
   const char* result = (num % 2 == 0) ? "짝수" : "홀수";

   printf("%d은(는) %s입니다.\n", num, result);

   return 0;
}
3은(는) 홀수입니다.

num 변수에 3을 넣어 놓은 후 (num % 2 == 0)을 물어보면 거짓(false)이 됩니다. 물음표(?) 기호 앞의 식이 false이기에 "홀수"가 result 변수에 담깁니다. 즉, 조건식이 참이면 "짝수"를 반환, 그렇지 않으면 "홀수"를 반환합니다. 참고로, 조건 연산자, 즉 3항 연산자는 앞으로 배울 제어문의 if 문으로 대체할 수 있습니다.

12.12 조건 연산자를 사용하여 최댓값 정하기

이번에는 조건 연산자를 사용하여 최댓값을 우리가 원하는 값으로 정하는 방법을 알아보겠습니다.

코드: operator_max_size.c

// operator_max_size.c
//[?] 들어오는 값이 20 이상이면 20으로 초기화, 20 미만이면 해당 값으로 초기화
#include <stdio.h>

int main(void)
{
   const int max_size = 20; // 최댓값을 20으로 고정(제한)
   int page_size = 0;

   page_size = 10;
   // 3항(조건) 연산자가 거짓이면 두 번째 식(page_size)이 반환 
   page_size = (page_size > max_size) ? max_size : page_size; // 10 
   printf("%d\n", page_size); // 10

   page_size = 50;
   // 3항(조건) 연산자가 참이면 첫 번째 식(max_size)이 반환 
   page_size = (page_size > max_size) ? max_size : page_size; // 20 
   printf("%d\n", page_size); // 20

   return 0;
}
10
20

pageSize 변수에 들어오는 값이 20보다 작으면 pageSize 값 그대로 출력하고, 20 이상이면 20으로 고정하는 기능을 구현해 보았습니다.

12.13 조건 연산자를 사용하여 문자 크기 비교하기

이번에는 조건 연산자를 사용하여 문자의 크기를 비교해 보겠습니다.

코드: ternary_operator.c

// ternary_operator.c
//[?] 조건 연산자를 사용하여 문자 크기 비교하기
#include <stdio.h>

int main(void)
{
   const char* result1 =
       ('A' < 'B') ? "'A'는 'B'보다 작습니다." : "A B C 순서로 커집니다.";
   const char* result2 =
       ('Z' < 'a')
       ?
       "'Z'는 'a'보다 작습니다."
       :
       "대문자보다 소문자가 더 큽니다.";

   printf("%s\n", result1); // 'A'는 'B'보다 작습니다.
   printf("%s\n", result2); // 'Z'는 'a'보다 작습니다.

   return 0;
}
'A'는 'B'보다 작습니다.
'Z'는 'a'보다 작습니다.

프로그래밍 언어에서는 A, B, C, …, Z, a, b, c, …, z 순서로 크기가 정해져 있습니다. 이러한 크기를 조건 연산자의 조건식에 두고 크기를 비교해 보았습니다.

12.14 조건(3항) 연산자로 절댓값 구하기

조건 연산자인 3항 연산자를 사용하면 다음 코드와 같이 정수의 절댓값을 편하게 구할 수 있습니다.

코드: absolute_value.c

// absolute_value.c
//[?] 3항 연산자(조건 연산자)를 사용하여 절댓값 구하기 
#include <stdio.h>

int main(void)
{
   int num = -21; // 음수 값을 저장
   int abs = (num < 0) ? -num : num; // 3항 연산자로 음수만 부호 변환
   printf("%d의 절댓값: %d\n", num, abs);

   return 0;
}
-21의 절댓값: 21

12.15 나열(콤마) 연산자

이미 변수 선언할 때 다루어 보았지만, 콤마를 구분자로하여 하나의 문장에 여러 개의 변수 선언문을 지정할 때 사용합니다. 이러한 콤마 연산자(Comma Operator)를 나열 연산자라고도 합니다. 나열 연산자는 다음과 같이 사용됩니다.

int a = 10, b = 20, c = 30;

위와 같이 콤마로 3개의 할당문이 지정되면, a, b, c 변수 모두 int 형 변수로 선언됩니다.

IMPORTANT

콤마 연산자는 코드가 복잡해질 수 있기에 가능하면 사용하지 않습니다.

12.16 sizeof 연산자로 데이터 타입의 크기 구하기

sizeof 연산자는 단항 연산자로서 데이터 타입 자체의 크기를 구하는데 사용됩니다. sizeof(int) 형태로 사용되며 int의 데이터 타입 크기인 4가 값으로 나옵니다. 참고로, C 언어 프로그래밍 언어는 sizeof 연산자의 결괏값이 플랫폼마다 다르게 나올 수 있습니다.

코드: sizeof_operator.c

// sizeof_operator.c
//[?] sizeof 연산자로 데이터 형식의 크기 구하기
#include <stdio.h>

int main(void)
{
   printf("sizeof(데이터 형식)\n");
   printf("  char 형식: %llu byte\n", sizeof(char));
   printf("   int 형식: %llu byte\n", sizeof(int));
   printf("  long 형식: %llu byte\n", sizeof(long));
   printf(" float 형식: %llu byte\n", sizeof(float));
   printf("double 형식: %llu byte\n", sizeof(double));

   return 0;
}
sizeof(데이터 형식)
  char 형식: 1 byte
   int 형식: 4 byte
  long 형식: 4 byte
 float 형식: 4 byte
double 형식: 8 byte

sizeof 연산자는 다른 연산자와 달리 sizeof() 형태로 괄호로 묶어주는 형태를 가집니다. C 언어의 기본 제공 형식의 크기는 예를 들어 double이라면 sizeof(double) 형태로 구할 수 있습니다.

12.17 연산자 우선 순위

여러 개의 연산자를 사용할 때에는 연산자의 우선순위(Precedence)에 따라서 계산됩니다. 예를 들면 다음과 같이 산술 연산자에서 +보다 *가 더 우선순위가 높습니다. 괄호 연산자인 ()연산자를 사용하면 우선순위를 변경할 수도 있습니다.

표: 우선순위가 적용된 산술 연산 구문

우선순위가 적용된 산술 연산 구문

다음은 C 언어에서 주요 연산자의 우선순위입니다.

표: C 언어에서의 주요 연산자 우선순위

연산자 우선순위

참고로, 연산자 우선 순위는 따로 외우거나 할 필요없이 괄호 연산자만 잘 써도 무관합니다. 나머지는 자연스레 익히면 됩니다. 다음 코드는 참고용으로 살펴보세요.

코드: operator_precedence.c

// operator_precedence.c
// C 언어의 연산자 우선 순위: 괄호(그룹) > 곱셈/나눗셈 > 덧셈/뺄셈 > 왼쪽에서 오른쪽
#include <stdio.h>

int main(void)
{
   printf("%d\n", 3 + 4 * 2); // 곱하기가 우선: 3 * 8 => 11
   printf("%d\n", (3 + 4) * 2); // 괄호가 우선: 7 * 2 => 14
   printf("%d\n", 10 / 5 * 2 + 1); // 왼쪽에서 오른쪽으로: 2 * 2 + 1 => 5
   printf("%d\n", 15 / (5 * (2 + 1))); // 안쪽 괄호가 우선: 15 / (5 * 3) => 1
   return 0;
}
11
14
5
1

12.18 장 요약

3항 연산자로 불리는 조건 연산자는 조건식이 참일 때와 거짓일 때에 따라서 서로 다른 값을 제공받고자할 때 사용됩니다. 이 연산자는 다음 장에서 배울 if 문으로 대체가 가능합니다. 기타, 콤마 연산자와 연산자 우선 순위를 이번 장에서 학습하였습니다. 연산자는 이 정도로 정리하고 제어문에 대해서 다음 장부터 학습해 나가도록 하겠습니다.

  • VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다.
  • 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다.
  • 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다.
  • 단, 링크와 SNS 공유 그리고 Youtube 동영상 공유는 허용합니다.
박용준 강사의 모든 유료 동영상 강의는 데브렉(devlec.com)에서 독점으로 제공됩니다.