본문 바로가기

비트 플래그와 비트 마스크 Bit flags, Bit masks 본문

💘 C++/연산자들

비트 플래그와 비트 마스크 Bit flags, Bit masks

Hyonii 2022. 1. 10. 14:52

비트 플래그와 비트 마스크 (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;

 

items_flag를 0으로 할당함

item1 을 가져 왔을 때는 00000001

item2 을 가져 왔을 때는 00000010

item3 을 가져 왔을 때는 00000100

.

.

.

총 아이템 8개를 갖고있는지 아닌지를 bool 타입 변수 8개를 사욯하는게 아니라

char형 1바이트 짜리 하나만 가지고 코딩하는 것이다.

 

아이템 8개의 상태를 변수 파라미터에 넣어주려고 한다면

bool타입 8개를 넣어줬어야 했는데

그냥 unsigned char 하나만 넣어주면 깔끔하게 해결됨

 

그 다음에 if문 만들거나 반복문 만들때 이렇게하면 코드 읽기도 편해진다.

 

먼저 작업해야할것은

각 비트에 대해서 플래그를 미리 설정을 해줘야한다.

 

이렇게 각 자리수에 따라서 1이 되어있는 자리가 다르다

 

비트 플래그를 이용해서 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비트 사용)에 저장된다.

 

https://www.rapidtables.com/web/color/RGB_Color.html

그래픽스 하거나 이미지처리 할 때 컬러테이블 많이 사용한다.

이 표의 특징은 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이다 이렇게 따로 분리를 하는 작업

 

golden rod 색을 예로 들어보자.

얘를 16진수로 표현한게 0xDAA520

16진수로 앞에 DA flag를 전부 1로 바꿔놓고 bitwise operator 로 DA만 추출하고 싶은것

 

FF를 이진수로 바꿔놓으면 앞에 1111 4개가 F 그 다음 1111 도 F
여기서 나오는 11111111은 mask

컬러값이 아니라 이 자리에 저장되는 숫자들이 RED 다, GREEN 이다, BLUE다 를 의미한다.

 

지금부터 pixel_color에서 이 mask를 이용해서 color값을 추출해보자

0xDAA520 에서 16진수 blue 20을 blue mask를 이용해서 추출해보면

 

blue 10진수로 32가 추출된 것을 볼 수 있다.

이제 GREEN을 추출해보면

틀림주의

 

green 이 다 0으로 찍힌것을 볼 수 있다.

green mask 는 00000000000000001111111100000000 이렇게 채워져있다

문제는 char 형은 뒤에 00000000까지밖에 못받는다.

그럼 integer로 바꿔서 출력해보면

 

green이 추출은 잘 되었는데&amp;amp;amp;amp;amp;amp;amp;amp;amp;nbsp;

문제는 char 타입 뒤에 00000000 자리 까지만 추출하고 앞에는 싹 날아가니까

10100101 을 00000000 자리까지 밀어줘야한다.

이때 shift 연산자를 사용한다.

0이 8개가 있기때문에 right shift 8칸만큼 밀어버리면 된다.

 

잘 옮겨짐&amp;amp;amp;amp;amp;amp;amp;amp;amp;nbsp;

앞에 00000000은 필요없으니까

int를 char로 바꿔주고 8개만큼만 출력해보면

 

unsigned char 형 green 이 잘 추출됨

red 까지 추출해보면

 

 

Comments