[비트플래그] bitflag에 대해 알아보자

jiyseo·2022년 5월 13일
0

코딩공부

목록 보기
1/1

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

  • 비트 켜기 비트 OR 연산자 ( | )를 이용하여 비트를 켤 수 있다.
    flag |= FLAG_POWER;
    위처럼 사용하며 이 의미는 flag 와 FLAG_POWER를 OR 연산하여 flag에 넣는다는 것을 의미한다. ex) 만약 flag가 0001 0000 이고 FLAG_POWER이 0100 0000 이라면 OR 연산 후 0101 0000 이 된다.
  • 비트 끄기 비트 AND 연산자( & )와 비트 NOT 연산자( ~ )를 이용해서 비트를 끌 수 있다.
    flag &= ~FLAG_POWER;
    이는 flag = (flag & ~FLAG_POWER) 와 같다. flag 와 FLAG_POWER를 NOT 연산 후 AND 연산하여 flag에 넣는다는 것을 의미한다. ex) 만약 flag가 0101 0000이고 FLAG_POWER가 0100 0000이라면 ~FLAG_POWER는 1011 1111 이 되고 AND 연산 후 0001 0000이 된다. 즉 두번째 비트를 끈 것이 된다.
  • 비트 뒤집기 비트 XOR 연산자( ^ )를 이용하여 비트를 뒤집는다.
    flag ^= FLAG_POWER;
    FLAG_POWER의 비트를 반전시키고 flag와 XOR연산 후 flag에 넣는다 ex) flag가 0001 0000이고 FLAG_POWER가 0100 0000이면 XOR후 flag은 0101 0000가 된다.
  • 비트가 켜져있는지 꺼져있는지 확인 비트 AND연산자 ( & )를 이용하여 확인 가능하다.
    putchar((val & mask) ? '1' : '0');
    만약 val 이 0101 1111 이고 이건 특정 비트만 마스크하는 것이 좋아 mask가 0100 0000이라면 & 연산후 0100 0000 이 되고 이는 참이므로 문자 '1'을 출력한다.

비트 플래그가 유용한 이유

  1. 옵션(or 상태)가 많이 필요할 때:

     bool 자료형 변수가 하나가 아니라 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);

    매개 변수 이름을 보고 옵션의 기능을 유추할 수 없을뿐더러 매개 변수의 목록이 너무 많다.

    someFunction(false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, fa

    option10과 option32가 true로 설정된 함수를 호출하려면 다음과 같이 해야한다.

bit_flag 실습

비트플래그를 공부하기 위해 간단한 실습을 진행했다. 우선 16진수로 각 변수들에 비트를 저장해주었다.

여기서 FLAG_LED는 FLAG_LED_R, FLAG_LED_B, FLAG_LED_G 를 OR 한 연산으로 0111 0000, 즉 0x70이 된다. 따라서 위의 세가지 중 하나라도 켜지면 FLAG_LED는 ON이 된다.

enum bit_flags {
	FLAG_MINUS  = 0x01, //0000 0001
	FLAG_PLUS	= 0x02, //0000 0010
	FLAG_SPACE	= 0x04, //0000 0100

	FLAG_LED_B	= 0x10, //0001 0000
	FLAG_LED_G	= 0x20, //0010 0000
	FLAG_LED_R	= 0x40, //0100 0000
	FLAG_POWER	= 0x80, //1000 0000

	FLAG_LED	= FLAG_LED_R | FLAG_LED_G | FLAG_LED_B, // FLAG_LED = 0x70 임
};

dump_bits 함수는 main에서 val을 키거나 끌 비트로 받아와 이를 이용하여 어떤 비트가 켜져있는지, 꺼져있는지 확인하고 출력함

void dump_bits(int val) {
	printf("%02X : ", val);
	dump_bitmap(val, 8);
	printf(" : ");
	dump_label(val);
	putchar('\n');
}

dump_bits 함수는 해당 예제가 어떤 비트를 가지고 있는지 0과 1로 표시해줌 bit count는 8로 받아와서 공백을 출력할 때 사용함

예를 들어 val이 1000 0000 이라고 가정해보자 mask는 1의 shift 연산자를 사용하여 7번 왼쪽으로 가야한다. 그러므로 1000 0000이 저장되고 >>를 이용하여 오른쪽으로 한칸씩 움직이며 0000 0000이 될 때까지 while문을 돌게 된다. 그리고 val 과 mask 를 & 연산자를 통해 어떤 비트가 켜져있는지 확인한다 처음 while 문을 돌 때 val과 mask가 둘 다 1000 0000이므로 1이 출력되고 두 번째로 돌 때에는 mask가 0100 0000이므로 0이 출력된다.

이처럼 반복하며 1000 0000을 출력하게 된다.

void dump_bitmap(int val, int bit_count) {
	unsigned int mask = 1 << --bit_count; // mask는 1000 0000

	while (mask) {
		putchar( (val & mask) ? '1' : '0');
		if (bit_count > 0 && bit_count-- % 4 == 0) putchar(' '); // 4자리마다 공백
		mask >>= 1;
	}
}

dump_label 함수는 해당 val과 위에서 enum으로 정의한 flag들을 &연산하여 해당 flag가 켜져있는지 확인한다.

예를들어 val이 1000 0000이라하자. FLAG_POWER는 1000 0000이기 때문에 참이고 그렇기에 ON을 출력하게 된다.

하지만 FLAG_LED_R의 경우 0100 0000이고 val과 &연산을 하면 0000 0000이기 때문에 거짓이고 OFF를 출력한다.

void dump_label(int val) {

	printf("POWER(%-3s) ", (val & FLAG_POWER) ? "ON" : "OFF");
	printf("LED_R(%-3s) ", (val & FLAG_LED_R) ? "ON" : "OFF");
	printf("LED_G(%-3s) ", (val & FLAG_LED_G) ? "ON" : "OFF");
	printf("LED_B(%-3s) ", (val & FLAG_LED_B) ? "ON" : "OFF");

	printf("LED(%-3s) ", (val & FLAG_LED) ? "ON" : "OFF");
//	printf("LED(%-3s) ", (val & (FLAG_LED_R|FLAG_LED_G|FLAG_LED_B)) ? "ON" : "OFF"); // 윗 문장과 같음
	printf("' '(%c) ", (val & FLAG_SPACE) ? 'o' : 'x');
	printf("'+'(%c) ", (val & FLAG_PLUS) ? 'o' : 'x');
	printf("'-'(%c) ", (val & FLAG_MINUS) ? 'o' : 'x');
}

main 함수는 다음과 같다.

예제 7개 실행 후 whlie 문을 통해 값을 받아 몇번의 비트를 킬것인지 끌 것인지 정할 수 있다.

int main()
{
	int flag = 0;
	int bit, num, sw;

	printf("T#1: ");
	flag |= FLAG_POWER; // 해당 bit 켜기
	dump_bits(flag);

	printf("T#2: ");
	flag |= FLAG_LED_R; // 해당 bit 켜기
	dump_bits(flag);

	printf("T#3: ");
	flag |= (FLAG_LED_G | FLAG_LED_B); // 2개 bit 동시 켜기
	dump_bits(flag);

	printf("T#4: ");
	flag &= ~FLAG_POWER; // 해당 bit 끄기, FLAG_POWER 비트를 ~로 부정하면 해당 bit를 0으로 하고 나머지는 모두 1이 됨
	dump_bits(flag);

	printf("T#5: ");
	flag &= ~(FLAG_LED_G | FLAG_LED_B); // 2개 bit 동시 끄기
	dump_bits(flag);

	printf("T#6: ");
	flag ^= FLAG_POWER; // 해당 bit 반전, XOR(exclusive OR, 배타적 OR)로 0은 1로 1은 0으로 반전함
	dump_bits(flag);

	printf("T#7: ");
	flag ^= FLAG_POWER; // 해당 bit 반전, XOR(exclusive OR, 배타적 OR)로 0은 1로 1은 0으로 반전함
	dump_bits(flag);

	while (1) {
		printf("  7      6      5      4      3      2      1      0\n");
		printf("POWER  LED_R  LED_G  LED_B         SPACE  PLUS   MINUS\n");
		printf("bits num(-1:exit) : ");
		scanf("%d", &num);
		if (num < 0) break;
		printf("switch(on=1,off=0) : ");
		scanf("%d", &sw);
		bit = 1 << num;
		if (sw == 0) {
			flag &= ~bit; // 0인 경우 flag를 끔
		} else {
			flag |= bit; // 0이 아닌 경우 flag를 켬
		}
		dump_bits(flag);
	}

    return 0;
}

while 문은 만약 7번인 FLAG_POWER를 끄도록 선택했다면 num에 7이 들어가게 되고 1111 1111을 왼쪽으로 7번 이동해서 1000 0000, 즉 FLAG_POWER가 된다. 이를 bit에 저장하고 0인 경우 flag를 끄고 0이 아닌 경우 flag를 켠다. 그리고 dump_bits() 함수에 flag를 보내 어떤 flag가 켜져있는지, 꺼져있는지 출력함.

실행결과

2를 입력하여 2를 켠다면

다음과 같으며 -1을 입력할 때까지 반복된다.

마지막 예제가 0100 0000이었기 때문에 처음 실행 시 LED_R도 켜져있다. LED는 LED R, B, G 중 하나라도 켜져있으면 ON이 나오도록 설정했으므로 ON이 되고

6번인 LED_R을 끈다면 LED도 함께 OFF가 된다.

profile
코딩뿡뿡이

0개의 댓글