2-2 비트 연산자

do·2022년 3월 9일
0

API

목록 보기
6/42

목차

  1. 비트
  2. 비트 연산자
  3. 비트 연산자의 대입 연산자
  4. MSB 부호비트
  5. 2의 보수법
  6. 비트 연산에 unsigned char 타입을 쓰는 이유
  7. 연산자 우선순위
  8. 비트 플래그
  9. 플래그를 사용한 비트 조작
  10. 비트 켜기, 끄기, 뒤집기, on/off 상태 확인하기
  11. 비트 플래그가 유용한 이유
  12. 비트 마스크
  13. 수강 신청 프로그램 course.c

비트

2진수를 저장하는 단위입니다. 컴퓨터가 사용할 수 있는 최소 단위이며 0과 1을 나타냅니다.
int temp = 8; 과 같이 정수를 메모리에 넣어준다면 (int는 4Byte의 메모리를 갖고 있으므로) 0000 0000 0000 0000 0000 0000 0001 0000 라고 저장됩니다.

비트 연산자

  1. 비트 단위로 논리 연산을 할 때 사용하는 연산자입니다. 또한, 비트 단위로 전체 비트를 왼쪽이나 오른쪽으로 이동시킬 때도 사용합니다.
  2. 특정 위치의 한 비트가 참인지 거짓인지 확인하거나 그 값을 변경할 경우에 사용합니다.
  3. 원하는 비트만큼 쉬프트하고, 그 데이터가 기존비트의 크기를 유지해야 한다면 유실된 비트의 수만큼 새로운 정보가 채워지는데 이것을 패딩이라 합니다.
  4. 과거에는 <<을 통해 곱셈 연산을, >>을 통해 나눗셈 연산을 수행한 결과를 표현하였다고 합니다.
  5. 비트 연산자를 적절한 때에 사용하면 메모리 공간의 효율성을 높이고, 연산의 수를 줄일 수 있습니다.
  6. 특히 비트 단위로 계산하기 때문에 일반적인 사칙연산 연산자보다 훨씬 속도가 빠릅니다.

1. 비트 NOT 연산자 ~ a
비트를 1이면 0으로, 0이면 1로 반전시킵니다.
unsigned를 사용하면 입력받은 값을 반전시키되, 부호 비트는 반전되지 않습니다.
2. 비트 AND 연산자 a & b
대응되는 비트가 모두 1이면 1을 반환합니다.
3. 비트 OR 연산자 a | b
대응되는 비트 중에서 하나라도 1이면 1을 반환합니다.
4. 비트 XOR 연산자 a ^ b
대응되는 비트가 서로 다르면 1을 반환합니다.
주로 암호화와 복호화에 사용되며, 특정 값을 0으로 초기화할 때도 유용하게 사용됩니다.
사용 사례.
1) unsigned int a = 2; 일 경우, a ^ a의 값은 0으로 출력됩니다.
2) 함수의 리턴값이 0일 경우, 레지스터를 xor연산하여 반환합니다.
3) 윈도우 환경에서 마우스 커서에 따라 발생하는 색상이 바뀌는 이벤트는 거의 xor연산을 사용합니다.
5. 비트 Shift 연산자 a << 3 a >> 3
<< 지정한 수만큼 비트를 전부 왼쪽으로 이동시킵니다.
>> 부호를 유지하면서 지정한 수만큼 비트를 전부 오른쪽으로 이동시킵니다.
계산법.
옮길 기준이 되는 비트열은 항상 연산자의 왼쪽, 얼마큼 옮길건지를 결정하는건 연산자의 오른쪽에 위치합니다. 만약 << 연산으로 왼쪽으로 비트열을 옮긴다면, 가장 오른쪽에서부터 옮긴 비트열 길이까지 0으로 채워집니다. 하지만 >> 연산으로 오른쪽으로 비트열을 옮긴다면, 가장 상위비트(MSB)를 왼쪽에서부터 채웁니다. (signed 변수에서의 Shift 연산은 결과값을 예측하기 어려워 거의 사용이 불가능합니다.)
예제.

0011 0110 << 3 = 1011 0000

└> 왼쪽으로 3비트를 이동시키니 001은 삭제되었습니다. 그리고 10110은 왼쪽으로 3비트 이동됩니다. 남은 자리 3개는 0으로 채웁니다.

0011 1100 >> 3 = 0000 0111

└> 오른쪽으로 3비트를 이동시키니 100은 삭제되었습니다. 그리고 00111은 오른쪽으로 3비트 이동됩니다. 옮기기 전 MSB는 0으로, 오른쪽으로 이동할 때는 이 MSB를 가지고 왼쪽 비트를 채우며 결국 000으로 채워집니다.

1001 1011 >> 4 = 1111 1001

└> 오른쪽으로 4비트를 이동시키니 1011은 삭제되었습니다. 그리고 1001은 오른쪽으로 4비트 이동됩니다. 옮기기 전 MSB는 1이므로, 오른쪽으로 이동할 때는 이 MSB를 가지고 왼쪽 비트를 채우며 결국 1111로 채워집니다.

비트 연산자의 대입 연산자

a = a & b;
a &= b; //위 코드와 같습니다.
a = a | b;
a |= b; //위 코드와 같습니다.

MSB 부호비트(상위비트)

0이면 양수, 1이면 음수를 나타냅니다.

2의 보수법

자료형에는 음수를 나타낼 수 있는 signed 타입과 unsigned 타입이 있습니다. 음수를 표현할 필요가 없다면 unsigned를 사용합니다.
다음은 음수를 나타낼 수 있는 signed 자료형에 대해 음수를 표현하는 방법입니다.

0011 1100

MSB가 0이므로 양수입니다. 그러니 정수로 표현하면 MSB를 제외하고 32+16+8+4 = 70 입니다.

1111 1001

하지만 위와 같이 MSB가 1이므로 음수입니다. 이때 2의 보수를 사용합니다.
1) MSB를 제외하고 비트를 반전시킵니다. (1의 보수법)
2) 1을 더해줍니다. (2의 보수법)
1)+2)의 방법에 따라 1111 1001의 음수값을 구해보면,

  1000 0110
+ 0000 0001
------------
  1000 0111

결과는 7이 나오는데, 이때 MSB는 여전히 1이므로 -7입니다.

비트 연산에 unsigned char 타입을 쓰는 이유

  1. char는 8비트 변수로써 signed의 경우 -128~127까지, unsigned의 경우 0~255까지 사용 가능합니다. int는 32비트 변수로써 -2,147,483,648 ~ 2,147,483,647까지 사용 가능합니다.
  2. 비트 연산을 쉽게 이해하기 위해 작은 숫자를 이용할 것이므로, 굳이 32비트까지 쓸 필요가 없습니다. 따라서 char을 이용합니다.
  3. signed의 경우, 부호 있는 정수입니다. 따라서 MSB가 1이 되면 음수로 계산되어 보수연산을 하게 되는데, unsigned를 이용하면 따로 보수연산 필요없이 부호비트에 영향이 가지 않습니다.

연산자 우선순위

비트 플래그

메모리의 최소 크기 단위는 1바이트이므로 변수의 크기는 적어도 1바이트 이상입니다. 8bit(1Byte)는 비트가 8개이므로 8가지 상태를 저장할 수 있습니다. 이는 11바이트를 사용해서 1비트만 사용하고 7비트를 낭비함으로써 1가지 상태만 저장하는 bool 자료형보다 효율적입니다.

바이트의 개별 비트를 비트 플래그(flag)라고 합니다.
플래그를 설명할 때는 일반적으로 오른쪽에서 왼쪽으로 셉니다.

0100 0001 //두 번째 비트와 여덟 번째 비트가 켜진 상태(on)

C++ 14에서 비트 플래그 정의.
비트 플래그를 사용하려면 바이트 내에서 개별 비트를 식별할 수 있는 방법을 사용해서 비트를 조작해야 합니다. 해당 비트를 나타내는 기호 상수를 정의하는 방식으로 조작할 수 있습니다.

const unsigned char option0 = 0b0000'0001; //represents bit 0
const unsigned char option1 = 0b0000'0010; //represents bit 1
const unsigned char option2 = 0b0000'0100; //represents bit 2
const unsigned char option3 = 0b0000'1000; //represents bit 3
...
const unsigned char option7 = 0b1000'0000; //represents bit 7

이제 각 비트 위치를 나타내는 일련의 기호 상수가 있습니다. 이 기호 상수를 비트를 조작하는 데 사용할 수 있습니다.
C++ 11 또는 이전 버전에서 비트 플래그 정의.
C++ 11은 바이너리 리터럴을 제공하지 않으므로 기호 상수를 설정하는 데 다른 방법을 사용해야 합니다.

const unsigned char option0 = 0x1;  //hex for 0000 0001
const unsigned char option1 = 0x2;  //hex for 0000 0010
const unsigned char option2 = 0x4;  //hex for 0000 0100
const unsigned char option3 = 0x8;  //hex for 0000 1000
const unsigned char option4 = 0x10; //hex for 0001 0000
const unsigned char option5 = 0x20; //hex for 0010 0000
const unsigned char option6 = 0x40; //hex for 0100 0000
const unsigned char option7 = 0x80; //hex for 1000 0000

<< 왼쪽 쉬프트 연산자를 사용한 더 쉬운 방법도 있습니다.

const unsigned char option0 = 1 << 0; //0000 0001
const unsigned char option1 = 1 << 1; //0000 0010
const unsigned char option2 = 1 << 2; //0000 0100
const unsigned char option3 = 1 << 3; //0000 1000
const unsigned char option4 = 1 << 4; //0001 0000
const unsigned char option5 = 1 << 5; //0010 0000
const unsigned char option6 = 1 << 6; //0100 0000
const unsigned char option7 = 1 << 7; //1000 0000

플래그를 사용한 비트 조작

사용하는 옵션 수에 따라 적절한 크기(8비트, 16비트, 32비트 등)의 부호 없는 정수 자료형을 사용합니다.

//위에서 정의한 8가지 옵션을 위해 8비트를 사용합니다.
unsigned char myflags = 0; //all bits turned off to start

비트 켜기, 끄기, 뒤집기, on/off 상태 확인하기

<--비트 켜기-->
myflags |= options4; //turn option4 on
myflags = 0000 0000
option4 = 0001 0000
-------------------
result  = 0001 0000
<--비트 끄기-->
myflags2 &= ~option4; //turn option4 off (myflags2 = myflags2 & ~option4 와 동일함)
myflags2 = 0001 1100
~option4 = 1110 1111
--------------------
result   = 0000 1100 //위의 result와 다르게 4번째 비트를 껐다
<--여러 비트 동시에 끄기-->
myflags2 &= ~(option4|option5); //turn options4 and option5 off at the same time
<--비트 뒤집기(토글)-->
myflags ^= option4; //
myflags ^= (option4|option5);
<--비트가 켜져있는지 꺼져있는지 확인하기-->
if (myflags & option4)
	printf("myflags has option4 set");
else if !(myflags & option5)
	printf("myflag does not have option5 set");

비트 플래그가 유용한 이유

1. 옵션이 많이 필요할 때
myflag같은 bool자료형 변수가 하나가 아니라 myflag1, myflag2... 처럼 n개의 옵션이 필요하다고 가정해볼 때, 8개의 옵션을 정의하려면 각각 true/false가 필요하므로 16개의 bool자료형 변수를 정의해야 하고, 16바이트 메모리를 사용합니다. 옵션이 많을수록 더 많은 메모리를 사용하는데, 비트 플래그를 사용하면 8개의 옵션을 사용할 때 1바이트(8비트)로 충분하며 메모리를 절약할 수 있습니다.
2. 옵션을 조합할 때
32가지 옵션을 이용해 사용하는 함수가 있다고 가정해볼 때, 이 함수를 호출하려면 32개의 매개변수를 사용해야 합니다.

void someFunc(bool option1, bool option2, .. bool option32);

매개변수 이름을 보고 옵션의 기능을 유추할 수 없을뿐더러 매개뱐수의 목록이 많습니다.
option2와 option32가 true로 설정된 함수를 호출하려면 다음과 같이 해야합니다.

someFunc(false, true, false, false, false, .. true);

읽기 매우 어렵고 어떤 매개변수가 어떤 옵션에 해당하는지 기억해야하고, 호출할 때마다 32개의 bool값을 복사하므로 성능이 안 좋을 수 있습니다.

아래와 같이 비트 플래그를 사용하여 함수를 정의하면, 원하는 옵션만 전달할 수 있습니다.

void newFunc(unsigned int options);
newFunc(option2|option32);

2개의 연산(비트 OR 연산 | 1개와 파라미터 복사본 1개)만 포함하기 때문에 성능이 더 좋습니다.

비트 마스크

플래그의 비트를 조작하거나 검사할 때 사용하는 숫자를 비트 마스크라고 합니다.
예제.
아래 예제는 사용자에게 숫자를 입력 받습니다. 그런 다음 비트 마스크를 사용하여 하위 4비트만 유지합니다.

#include <stdio.h>
int main()
{
	const unsigned int lowMask = 0xF; //hex for 0000 0000 0000 1111
    printf("Enter an integer: ");
    //
    int num;
    scanf("%d", &num);
    num &= lowMask; //remove the high bits to leave only the low bits
    printf("The 4 low bits have value: %d", num);
    return 0;
}

출력.
Enter an integer: 151
The 4 low bits have value: 7
설명.
151은 바이너리로 1001 0111 입니다. lowMask는 8비트 바이너리로 0000 1111 입니다.
1001 0111 & 0000 1111 = 0000 011 이므로, 10진수 7을 출력합니다.

수강 신청 프로그램 course.c





0개의 댓글