03.08 - 비트 플래그와 비트 마스크 (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)라고 한다.
플래그를 설명할 때는 일반적으로 오른쪽에서 왼쪽으로 센다. 예를 들어, 아래는 두 번째와 여덟 번째 비트가 켜진 상태다.
0100 0001 // 두 번째 비트와 여덟 번째 비트가 켜진 상태(on)
C++ 14에서 비트 플래그 정의 (Defining bit flags in C++14)
비트 플래그를 사용하려면 바이트 내에서 개별 비트를 식별할 수 있는 방법을 사용해서 비트를 조작해야 한다. 해당 비트를 나타내는 기호 상수를 정의하는 방식으로 조작할 수 있다.
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 또는 이전 버전에서 비트 플래그 정의 (Defining bit flags in C++11 or earlier)
C++ 11은 바이너리 리터럴을 제공하지 않으므로 기호 상수를 설정하는 데 다른 방법을 사용해야 한다.
16진수를 사용하는 게 일반적인 방법이다:
const unsigned char option0 = 0x1;
const unsigned char option1 = 0x2;
const unsigned char option2 = 0x4;
const unsigned char option3 = 0x8;
const unsigned char option4 = 0x10;
const unsigned char option5 = 0x20;
const unsigned char option6 = 0x40;
const unsigned char option7 = 0x80;
읽기는 조금 어렵지만, 왼쪽 시프트 연산자(<<
)를 사용한 더 쉬운 방법도 있다:
const unsigned char option0 = 1 << 0;
const unsigned char option1 = 1 << 1;
const unsigned char option2 = 1 << 2;
const unsigned char option3 = 1 << 3;
const unsigned char option4 = 1 << 4;
const unsigned char option5 = 1 << 5;
const unsigned char option6 = 1 << 6;
const unsigned char option7 = 1 << 7;
플래그를 사용한 비트 조작 (Using bit flags to manipulate bits)
다음으로 필요한 것은 조작하고자 하는 변수다. 사용하는 옵션(option) 수에 따라 적절한 크기(8비트, 16비트, 32비트 등)의 부호 없는(unsigned
) 정수 자료형을 사용한다.
unsigned char myflags = 0;
비트 켜기 (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;
myflags ^= ;
비트가 켜져 있는지 꺼져 있는지 확인하기 (Determining if a bit is on or off)
비트 AND 연산자(&
)를 이용해서 비트의 상태를 알 수 있다.
if (myflags & option4)
std::cout << "myflags has option 4 set";
if !(myflags & option5)
std::cout << "myflags does not have option 5 set";
비트 플래그가 유용한 이유 (Why are bit flags useful?)
1. 옵션(or 상태)가 많이 필요할 때:
myflag
같은 bool 자료형 변수가 하나가 아니라 myflag1
, myflag2
... 처럼 n개의 옵션이 필요하다고 가정해보자. 8개의 옵션(or 상태)을 정의하려면 각각 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);
읽기 매우 어렵고 어떤 매개 변수가 어떤 옵션에 해당하는지 기억해야 한다. 또한, 호출할 때마다 32개의 bool 값을 복사해야 하므로 성능이 안 좋을 수 있다.
대신, 다음과 같이 비트 플래그를 사용하여 함수를 정의하면:
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
는 다음과 같이 정의되어 있다:
#define GL_DEPTH_BUFFER_BIT 0x00000100
#define GL_STENCIL_BUFFER_BIT 0x00000400
#define GL_COLOR_BUFFER_BIT 0x00004000
아래는 게임에 대한 추상적인 코드다:
#include <iostream>
int main()
{
const unsigned char isHungry = 1 << 0;
const unsigned char isSad = 1 << 1;
const unsigned char isMad = 1 << 2;
const unsigned char isHappy = 1 << 3;
const unsigned char isLaughing = 1 << 4;
const unsigned char isAsleep = 1 << 5;
const unsigned char isDead = 1 << 6;
const unsigned char isCrying = 1 << 7;
unsigned char me = 0;
me |= isHappy | isLaughing;
me &= ~isLaughing;
std::cout << "I am happy? " << static_cast<bool>(me & isHappy) << '\n';
std::cout << "I am laughing? " << static_cast<bool>(me & isLaughing) << '\n';
return 0;
}
std::bitset
C++ 표준 라이브러리는 비트 플래그 조작을 돕는 std::biset
을 제공한다.
std::bitset
을 사용하려면 <bitset>
헤더 파일을 #include
한 다음 필요한 비트 수를 나타내는 std::bitset
변수를 정의해야 한다.
#include <bitset>
std::bitset<8> bits;
필요한 경우 초기값 집합을 사용하여 비트 집합을 초기화할 수 있다.
#include <bitset>
std::bitset<8> bits(option1 | option2) ;
std::bitset<8> morebits(0x3) ;
초기값은 이진수로 해석되므로 값 3
은 0000 0011
이 된다.
std:bitset
은 4가지 주요 함수를 제공한다.
test()
: 비트의 상태를 알려준다.
set()
: 비트를 켠다.
reset()
: 비트를 끈다.
flip()
: 비트를 뒤집는다.
#include <bitset>
#include <iostream>
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);
bits.set(option4);
bits.flip(option5);
bits.reset(option5);
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
은 표준 비트 단위 연산자(|
, &
, ^
)도 지원하므로 원하는 경우 사용할 수 있다. bitset
이 더 편리하고 오류 발생률이 낮으므로 사용하는 것을 추천한다.
비트 마스크 (bit mask)
비트 플래그의 원칙은 비트 단위 연산 한 번으로 여러 비트를 한 번에 켜거나, 끄거나, 뒤집거나, 검사하도록 확장할 수 있다.
플래그의 비트를 조작하거나 검사할 때 사용하는 숫자를 비트 마스크(bit mask)라고 부른다.
비트 마스크를 사용한 예제를 보자. 다음 예제는 사용자에게 숫자를 입력받는다. 그런 다음 비트 마스크를 사용하여 하위 4비트 만 유지한다.
#include <iostream>
int main()
{
const unsigned int lowMask = 0xF;
std::cout << "Enter an integer: ";
int num;
std::cin >> num;
num &= lowMask;
std::cout << "The 4 low bits have value: " << num << '\n';
return 0;
}
Enter an integer: 151
The 4 low bits have value: 7
151
은 바이너리(binary)로 1001 0111
이다. lowMask
는 8비트 바이너리로 0000 1111
이다. 1001 0111 & 0000 1111 = 0000 0111
이므로 10진수 7
을 출력한다.
위 예제는 보기에 매우 인위적이지만, 한 번의 작업으로 여러 개의 비트를 수정할 수 있다는 의미가 중요하다.
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비트 사용)에 저장된다.
32-bit RGBA value |
|
|
|
bits 31-24 |
bits 23-16 |
bits 15-8 |
bits 7-0 |
RRRRRRRR |
GGGGGGGG |
BBBBBBBB |
AAAAAAAA |
red |
green |
blue |
alpha |
다음 프로그램은 사용자로부터 32비트 16진수 값을 입력받아서 R, G, B 및 A의 8비트 색 값을 추출한다.
#include <iostream>
int main()
{
const unsigned int redBits = 0xFF000000;
const unsigned int greenBits = 0x00FF0000;
const unsigned int blueBits = 0x0000FF00;
const unsigned int alphaBits = 0x000000FF;
std::cout << "Enter a 32-bit RGBA color value in hexadecimal (e.g. FF7F3300): ";
unsigned int pixel;
std::cin >> std::hex >> pixel;
unsigned char red = (pixel & redBits) >> 24;
unsigned char green = (pixel & greenBits) >> 16;
unsigned char blue = (pixel & blueBits) >> 8;
unsigned char alpha = pixel & alphaBits;
std::cout << "Your color contains:\n";
std::cout << static_cast<int>(red) << " of 255 red\n";
std::cout << static_cast<int>(green) << " of 255 green\n";
std::cout << static_cast<int>(blue) << " of 255 blue\n";
std::cout << static_cast<int>(alpha) << " of 255 alpha\n";
return 0;
}
Enter a 32-bit RGBA color value in hexadecimal (e.g. FF7F3300): FF7F3300
Your color contains:
255 of 255 red
127 of 255 green
51 of 255 blue
0 of 255 alpha
비트 AND 연산자 (&
)를 사용해 필요한 8비트 집합을 검사한 다음 오른쪽 시프트 연산자 (>>
)를 이용해 0~255 범위로 이동시킨다.
요약 (Summary)
비트 플래그 켜기, 끄기, 뒤집기, 검사하기 요약:
비트 AND 연산자(&
)를 이용해서 비트의 상태를 알 수 있다.
if (myflags & option4) ... // if option4 is set, do something
비트 OR 연산자(|
)를 사용해 비트를 켤 수 있다.
myflags |= option4; // turn option 4 on.
myflags |= (option4 | option5); // turn options 4 and 5 on.
비트 AND 연산자(&
)와 비트 NOT 연산자(~
)를 이용해서 비트를 끌 수 있다.
myflags &= ~option4; // turn option 4 off
myflags &= ~(option4 | option5); // turn options 4 and 5 off
비트 XOR 연산자(^
)를 이용해서 비트를 토글(toggle)할 수 있다.
myflags ^= option4; // flip option4 from on to off, or vice versa
myflags ^= (option4 | option5); // flip options 4 and 5
이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/3-8a-bit-flags-and-bit-masks/ 입니다.