비트 플래그와 비트 마스크 Bit flags, Bit masks 본문
비트 플래그와 비트 마스크 (Bit flags and bit masks)
비트 플래그 (bit flag)
메모리의 최소 크기 단위는 1바이트이므로 변수의 크기는 적어도 1바이트 이상이다.
8비트(1바이트)는 비트가 8개이므로 8가지 상태를 저장할 수 있다.
이는 1바이트를 사용해서 1비트만 사용하고 7비트를 낭비함으로써
1가지 상태만 저장하는 bool 자료형보다 훨씬 효율적이다.
플래그(flag)는 깃발에서 유래한 용어다.
보통 깃발을 위로 올리면 On, 아래로 내리면 Off를 뜻한다.
이걸 정수의 비트에 활용하는 건데 비트가 1이면 ON, 0이면 OFF를 나타낸다.
여기서 바이트의 개별 비트를 비트 플래그(bit flag)라고 한다.
C++ 14에서 비트 플래그 정의 (Defining bit flags in C++14)
비트 플래그를 사용하려면 바이트 내에서 개별 비트를 식별할 수 있는 방법을 사용해서 비트를 조작해야 한다.
해당 비트를 나타내는 기호 상수를 정의하는 방식으로 조작할 수 있다.
// Define 8 separate bit flags (these can represent whatever you want)
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 option4 = 0b0001'0000; // represents bit 4
const unsigned char option5 = 0b0010'0000; // represents bit 5
const unsigned char option6 = 0b0100'0000; // represents bit 6
const unsigned char option7 = 0b1000'0000; // represents bit 7
이제 각 비트 위치를 나타내는 일련의 기호 상수가 있다.
이 기호 상수를 비트를 조작하는 데 사용할 수 있다.
C++ 11 또는 이전 버전에서 비트 플래그 정의
C++ 11은 바이너리 리터럴을 제공하지 않으므로 기호 상수를 설정하는 데 다른 방법을 사용해야 한다.
16진수를 사용하는게 일반적인 방법이다.
// Define 8 separate bit flags (these can represent whatever you want)
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
왼쪽 shift 연산자(<<) 를 사용한 더 쉬운 방법도 있다.
// Define 8 separate bit flags (these can represent whatever you want)
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
플래그를 사용한 비트 조작 (Using bit flags to manipulate bits)
다음으로 필요한 것은 조작하고자 하는 변수다.
사용하는 옵션(option) 수에 따라 적절한 크기(8비트, 16비트, 32비트 등)의 부호 없는 (unsigned) 정수 자료형을 사용한다.
// 위에서 정의한 8가지 옵션을 위해 8비트를 사용한다.
unsigned char myflags = 0; // all bits turned off to start
비트 켜기 (Turning individual bits on)
비트 OR 연산자(|) 를 사용해 비트를 켤 수 있다.
myflags |= option4; // turn option 4 on
myflags |= option4는 myflags = (myflags | option4)와 같다
myflags = 0000 0000 //we initialized this to 0
option4 = 0001 0000
-------------------
result = 0001 0000
비트 끄기 (Turning individual bits off)
비트 AND 연산자(&)와 비트 NOT 연산자(~)를 이용해서 비트를 끌 수 있다.
myflags &= ~option4; // turn option 4 off
myflags 가 처음에 0001 1100 이라고 가정하자. (option3,4 및 5가 켜진상태)
myflags &= ~option4 는 myflags = (myflags & ~option4) 와 같다.
myflags = 0001 1100
~option4 = 1110 1111
--------------------
result = 0000 1100
그래서 0000 1100 이 다시 할당된다.
즉, 네번째 비트를 끈 것이다.
여러 비트를 동시에 끌 수도 있다.
myflags &= ~(option4 | option5); // turn options 4 and 5 off at the same time
비트 뒤집기 (Flipping individual bits)
비트 XOR 연산자(^)를 이용해서 비트를 토글(toggle) 할 수 있다.
myflags ^= option4; // flip option4 from on to off, or vice versa
myflags ^= (option4 | option5); // flip options 4 and 5 at the same time
비트가 켜져 있는지 꺼져 있는지 확인하기
비트 AND 연사자(&)를 이용해서 비트의 상태를 알 수 있다.
if (myflags & option4)
std::cout << "myflags has option 4 set";
if !(myflags & option5)
std::cout << "myflags does not have option 5 set";
비트 플래그가 유용한 이유
아이템이 4개 나오는 게임을 만든다고 가정해보면
아이템 플래그를 바꿔가면서 코딩했는데 코드가 복잡하다
이렇게 코딩했을 경우 만약 아이템이 32개 있을 때 플래그가 bool 값으로 32개가 있어야 하니 불편하다
함수로 이벤트를 발생시키는 함수를 만든다면
아이템의 소지 여부, 어떤 것을 갖고있나 넣어줘야 되는데
이때 아이템 32개를 갖고있는지 아닌지 다 넣어주려면
파라미터로
invokeEvent(item1_flag, item2_flag, item3_flag, ....); 이렇게 쭉 넣어야 한다.
파라미터가 아이템 개수와 똑같아지는 문제가 발생한다.
지금부터 하는 방법이 array를 compact하게 쓸 수 있는 방법이라고 생각하면 된다.
bitgflag는 게임프로그래밍 할 때 잘 쓰인다.
bitflag를 이용하는 방법
우리가 사용하는 변수들 1byte만 해도 8bit니까
8가지 on/off를 표시할 수 있다.
true false를 8가지나 표시할 수 있다.
그러니까 이걸 잘 활용하면
1바이트짜리 변수 하나만 사용해도
아이템 8개를 갖고있는지 아닌지 정도는 데이터로 표현,저장 할 수 있다.
이번에는 아이템이 최대 8개 있다고 가정하고
char형 1바이트짜리 하나만 써보면
unsigned char items_flag = 0;
item1 을 가져 왔을 때는 00000001
item2 을 가져 왔을 때는 00000010
item3 을 가져 왔을 때는 00000100
.
.
.
총 아이템 8개를 갖고있는지 아닌지를 bool 타입 변수 8개를 사욯하는게 아니라
char형 1바이트 짜리 하나만 가지고 코딩하는 것이다.
아이템 8개의 상태를 변수 파라미터에 넣어주려고 한다면
bool타입 8개를 넣어줬어야 했는데
그냥 unsigned char 하나만 넣어주면 깔끔하게 해결됨
그 다음에 if문 만들거나 반복문 만들때 이렇게하면 코드 읽기도 편해진다.
먼저 작업해야할것은
각 비트에 대해서 플래그를 미리 설정을 해줘야한다.
비트 플래그를 이용해서 item 0 을 가졌나 안가졌나 item 2를 가졌나 안가졌나 표시할 수 있다.
지나가다가 이벤트가 발생해서 아이템 0번을 얻게되는 상황 예시
아이템 3을 얻었을 때도 마찬가지이다.
아이템 3을 잃었을때
구현 하는 방법은 bitwise and, bitwise not을 붙여서 하면 된다.
아이템1을 갖고있는지 알고싶을 때
bitwise and를 쓰면 된다.
opt1 과 items_flag를 비교를 해서
opt1 자리에 해당하는 이진수숫자가 1이면 (items_flag & opt1) 이 true
동시에 아이템을 2개 , 3개도 가질 수 있다.
아이템이 100개쯤 되면 for문 돌리면 된다. 걱정 ㄴㄴ
아이템2를 가지고 있고 아이템 1을 가지고 있지 않을 때,
이벤트가 발생해서 아이템2는 잃게하고 아이템 1을 갖게 되는 상태를 표현해보면
상태를 바꿔주는 것은 bitwise xor을 사용한다.
1 2 자리 숫자가 바뀐 것을 볼 수 있다.
바꿔주는거는 bitwise xor 을 사용한다!
1. 옵션(or 상태)가 많이 필요할 때
myflag 같은 bool형 변수가 하나가 아니라 myflag1, myflag2 ... 처럼 n개의 옵션이 필요하다고 가정해보자.
8개의 옵션을 정의하려면 각각 true, false가 필요하므로 16개의 bool형 변수를 정의해야 하고.
16바이트 메모리를 사용한다. ( 옵션이 많을수록 더 많은 메모리를 사용한다.)
그러나 비트 플래그를 사용하면 8개의 옵션을 사용할 때, 1바이트(8비트)로 충분하다. 즉 메모리 절약 가능
2. 옵션(or 상태)을 조합할 때
32가지 옵션을 이용해 사용할 수 있는 함수가 있다고 가정해보자.
이 함수를 호출하려면 32개의 매개 변수 ( parameter ) 를 사용할 것이다.
void someFunction(bool option1, bool option2, bool option3, bool option4, bool option5, bool option6, bool option7, bool option8, bool option9, bool option10, bool option11, bool option12, bool option13, bool option14, bool option15, bool option16, bool option17, bool option18, bool option19, bool option20, bool option21, bool option22, bool option23, bool option24, bool option25, bool option26, bool option27, bool option28, bool option29, bool option30, bool option31, bool option32);
매개 변수 이름을 보고 옵션의 기능을 유추할 수 없을 뿐더러 매개 변수의 목록이 너무 많다.
option10과 option32가 true로 설정된 함수를 호출하려면 다음과 같이 해야한다.
someFunction(false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true);
대신, 다음과 같이 비트 플래그를 사용하여 함수를 정의하면
void someFunction(unsigned int options);
비트 플래그를 이용해서 원하는 옵션만 전달 할 수 있다.
someFunction(option10 | option32);
이 같은 방법은 읽기 훨씬 더 쉬울 뿐만 아니라,
2개의 연산(비트 OR 연산 1개와 파라미터 복사본 1개)만 포함하기 때문에 더 성능이 좋다.
또한 나중에 옵션을 추가해야 할 때에는 비트 플래그를 정의하기만 하면 된다.
Bit flags in real life
3D 그래픽 라이브러리인 'OpenGL'에서 몇몇 함수는 비트 플래그를 매개 변수로 사용한다.
glClearg (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the color and the depth buffer
이런식의 bitwise or 가 있는 코딩을 꽤 많이 하게 된다
GL_COLOR_BUFFER_BIT와 GL_DEPTH_BUFFER_BIT는 다음과 같이 정의되어 있다:
#define GL_DEPTH_BUFFER_BIT 0x00000100
#define GL_STENCIL_BUFFER_BIT 0x00000400
#define GL_COLOR_BUFFER_BIT 0x00004000
std::bitset
C++ 표준 라이브러리는 비트 플래그 조작을 돕는 std::bitset을 제공한다.
#include <bitset>
std::bitset<8> bits; // we need 8 bits
필요한 경우 초기값 집합을 사용하여 비트 집합을 초기화 할 수 있다.
#include <bitset>
std::bitset<8> bits(option1 | option2) ; // start with option 1 and 2 turned on
std::bitset<8> morebits(0x3) ; // start with bit pattern 0000 0011
초기값은 이진수로 해석되므로 값 3은 0000 0011이 된다.
std::bitset 은 4가지 주요 함수를 제공한다.
- test() : 비트의 상태를 알려준다.
- set() : 비트를 켠다
- reset() : 비트를 끈다
- flip() : 비트를 뒤집는다.
#include <bitset>
#include <iostream>
// Note that with std::bitset, our options correspond to bit indices, not bit patterns
const int option0 = 0;
const int option1 = 1;
const int option2 = 2;
const int option3 = 3;
const int option4 = 4;
const int option5 = 5;
const int option6 = 6;
const int option7 = 7;
int main()
{
std::bitset<8> bits(0x2); // we need 8 bits, start with bit pattern 0000 0010
bits.set(option4); // set bit 4 to 1 (now we have 0001 0010)
bits.flip(option5); // flip bit 5 (now we have 0011 0010)
bits.reset(option5); // set bit 5 back to 0 (now we have 0001 0010)
std::cout << "Bit 4 has value: " << bits.test(option4) << '\n';
std::cout << "Bit 5 has value: " << bits.test(option5) << '\n';
std::cout << "All the bits: " << bits << '\n';
return 0;
}
//This prints:
Bit 4 has value: 1
Bit 5 has value: 0
All the bits: 00010010
std::bitset을 std::cout을 이용해 출력하면 모든 비트 값이 출력된다.
std:bitset은 표준 비트 단위 연산자(|, &, ^)도 지원하므로 원하는 경우 사용할 수 있다.
비트 마스크 (bit mask)
비트 플래그의 원칙은 비트 단위 연산 한 번으로 여러 비트를 한번에 켜거나, 끄거나, 뒤집거나, 검사하도록 확장할 수 있다.
플래그의 비트를 조작하거나 검사할 때 사용하는 숫자를 비트 마스크 (bit mask)라고 부른다.
비트 마스크를 사용한 예제를 보자.
RGBA Color Example
TV와 모니터 같은 컬러 디스플레이 장치는 각각 색 점을 표시할 수 있는 수백만 개의 픽셀로 구성되어 있다.
색상의 점은 세개의 광선(빨간 색, 녹색, 파란 색: RGB)으로 구성된다.
색상 강도를 변경하면 색상 스펙트럼의 모든 색상을 만들 수 있다.
일반적으로 특정 픽셀의 R, G, B양은 8비트 기호 없는(unsgiend) 정수로 표시된다.
예를 들면,
빨간 색: (R: 255, G: 0, B: 0),
보라 색: (R: 255, G: 0, B: 255),
회 색: (R=127, G=127, B=127)이 있다.
픽셀에 색을 할당할 때 RGB와 함께 4번 째 값 'A'도 쓰인다.
A는 'Alpha'를 의미하며 색이 얼마나 투명한지를 제어한다.
A=0이면 완전히 투명하고, A=255면 완전히 불투명하다.
R, G, B 및 A는 일반적으로 32비트 정수(각 구성 요소에 8비트 사용)에 저장된다.
그래픽스 하거나 이미지처리 할 때 컬러테이블 많이 사용한다.
이 표의 특징은 RED, GREEN, BLUE 컬러를 16진수로 표현한다.
RR레드, GG그린, BB블루
16진수 FF는 255를 의미
0에서 부터 255까지 숫자를 이용해서 컬러의 강도를 나타낸다
예를들어 골드#FFD700 (255,215,0) 면
RED가 255로 가장 쎈 상태인거고
GREEN이 255보다 좀 작은 215로 그린 성분이 꽤 많은 편이고
BLUE는 0으로 골드색을 표현할때 거의 안들어간다고 보면 됨
FF는 1바이트
세개면 3바이트이다.
보통 RED, GREEN, BLUE + Alpha 까지 해서 4바이트이다.
integer와 딱 맞음
디자이너들은 거의 1바이트 세개로 표현을 많이 한다.
지금 해보려는 거는
입력이 16진수 숫자일 경우에 레드, 그린, 블루 따로 들어오는게 아니라 16진수 숫자 하나로 들어오는데
레드는 255고 그린은 215고 블루는 0이다 이렇게 따로 분리를 하는 작업
얘를 16진수로 표현한게 0xDAA520
16진수로 앞에 DA flag를 전부 1로 바꿔놓고 bitwise operator 로 DA만 추출하고 싶은것
컬러값이 아니라 이 자리에 저장되는 숫자들이 RED 다, GREEN 이다, BLUE다 를 의미한다.
지금부터 pixel_color에서 이 mask를 이용해서 color값을 추출해보자
0xDAA520 에서 16진수 blue 20을 blue mask를 이용해서 추출해보면
이제 GREEN을 추출해보면
틀림주의
green mask 는 00000000000000001111111100000000 이렇게 채워져있다
문제는 char 형은 뒤에 00000000까지밖에 못받는다.
그럼 integer로 바꿔서 출력해보면
문제는 char 타입 뒤에 00000000 자리 까지만 추출하고 앞에는 싹 날아가니까
10100101 을 00000000 자리까지 밀어줘야한다.
이때 shift 연산자를 사용한다.
0이 8개가 있기때문에 right shift 8칸만큼 밀어버리면 된다.
앞에 00000000은 필요없으니까
int를 char로 바꿔주고 8개만큼만 출력해보면
red 까지 추출해보면
'💘 C++ > 연산자들' 카테고리의 다른 글
비트단위 연산자 Bitwise Operators (0) | 2022.01.08 |
---|---|
이진수 Binary Numbers (0) | 2022.01.07 |
논리 연산자 Logical Operators (0) | 2022.01.03 |
관계연산자 Relational Operators (0) | 2022.01.03 |
Sizeof, 쉼표 연산자, 조건부 연산자 (0) | 2022.01.02 |