BitMask / BitFlags

열수철·2025년 1월 10일

C++로 개발하다 보면, 비트 연산을 꽤 자주 쓰게 됩니다. 특히 상태나 옵션을 여러 개 동시에 표현해야 할 때, BitmaskBit flags(비트 플래그)가 상당히 편리하죠. 이번 포스팅에서는 Bitmask, Bit flags 개념부터 기본 연산, 사용 예시까지 한 번에 정리해보려고 합니다.


1. Bitmask와 Bit Flags란?

  • Bitmask
    특정 비트를 골라내거나, 반대로 특정 비트들만 집중적으로 다루기 위해 사용하는 “Mask” 같은 값입니다. 예를 들어 0xFF(16진수 255)는 하위 8비트만 “가려내서” 사용하고 싶을 때 쓰는 등, 원하는 비트 영역만 추려낼 수 있죠.

  • Bit Flags(비트 플래그)
    정수 타입의 각 비트를 ON/OFF 스위치처럼 써서, 여러 상태나 옵션을 한 변수에 담아 표현하는 방법입니다. 예를 들어,

    • 0번째 비트 → 정지(Stop)
    • 1번째 비트 → 걷기(Walk)
    • 2번째 비트 → 달리기(Run)
      이런 식으로 미리 정의해두고, 한 변수(status)에 여러 상태를 겹쳐서 표현할 수 있어요.

언제 쓰면 좋을까?

  1. 상태가 여러 개라서 Boolean 변수를 너무 많이 써야 할 때
  2. 메모리를 조금이라도 아껴야 할 때 (특히 임베디드, 게임 등)
  3. 빠른 연산이 필요한 경우 (비트 연산은 CPU 차원에서 매우 가볍습니다)

2. 비트 플래그 예시

다음 코드는 RPG 게임 캐릭터의 상태를 비트 플래그로 표현하는 간단한 예시입니다.

#include <iostream>

// 비트 플래그 정의
constexpr int STOP   = 1 << 0; // 0001 (2^0 = 1)
constexpr int WALK = 1 << 1; // 0010 (2^1 = 2)
constexpr int RUN     = 1 << 2; // 0100 (2^2 = 4)

int main() {
    int status = 0; // 처음엔 어떤 상태도 없음

    // 정지와 걷기 상태를 추가
    status |= STOP;
    status |= WALK;

    // 현재 상태 확인
    if (status & STOP) {
        std::cout << "정지 상태입니다.\n";
    }
    if (status & WALK) {
        std::cout << "걷는 중입니다.\n";
    }

    // 마비 상태를 해제하기
    status &= ~RUN;

    // 다시 확인
    if (status & RUN) {
        std::cout << "달리는 중입니다.\n";
    } else {
        std::cout << "달리기를 멈춥니다.\n";
    }

    return 0;
}
  • status |= FLAG; : status 변수의 0번째 비트를 켜서(1로 만들어서) FLAG를 작동합니다.
  • status &= ~FLAG; : status 변수의 1번째 비트를 꺼서(0으로 만들어서) FLAG를 해제합니다.

이처럼 플래그(FLAG)는 1 << N 형태로 정의해서 쓰는 게 가장 흔한 방법이에요.


3. 자주 쓰는 비트 연산

비트 플래그를 활용하기 위해서는 기본적인 비트 연산을 익혀두면 편해요.

  1. 특정 비트를 켜기(Set)

    status |= FLAG;

    예: status |= (1 << 3); // 3번째 비트 켜기

  2. 특정 비트를 끄기(Clear)

    status &= ~FLAG;

    예: status &= ~(1 << 3); // 3번째 비트 끄기

  3. 특정 비트를 토글(Toggle)

    status ^= FLAG;

    예: status ^= (1 << 3); // 3번째 비트가 켜져있으면 끄고, 꺼져있으면 켠다

  4. 특정 비트가 켜져있는지 확인(Check)

    if (status & FLAG) {
        // 켜져있다면...
    }

    예:

    if (status & (1 << 3)) {
        std::cout << "3번째 비트가 켜져 있습니다.\n";
    }

4. Bitmask와 함께 쓰기 좋은 팁

  1. enum, constexpr로 플래그 정의하기
    위 예시처럼 constexpr int로 정의하는 방법이 가장 간단해요. C++11 이상의 enum class를 써서 형 안전성(Strongly typed)을 좀 더 챙기는 경우도 있습니다. 예를 들어:

    enum class StatusFlags : int {
        STOP   = 1 << 0,
        WALK   = 1 << 1,
        RUN    = 1 << 2,
        // ...
    };

    이렇게 하면, static_cast<int>(StatusFlags::RUN) 같은 식으로 캐스팅을 해줘야 하긴 하지만, 중복된 상수 이름 충돌을 줄일 수 있다는 장점이 있어요.

  2. 연산자 오버로딩
    enum class로 만들면 그냥 |= 같은 비트 연산이 바로 안 되기 때문에, 연산자 오버로딩을 통해 “코드 가독성”을 높이는 패턴도 있습니다.

    inline StatusFlags operator|(StatusFlags a, StatusFlags b) {
        return static_cast<StatusFlags>(static_cast<int>(a) | static_cast<int>(b));
    }

    이렇게 해주면 StatusFlags f = StatusFlags::STOP | StatusFlags::WALK; 같은 표현이 가능해지죠.

  3. 플래그가 너무 많다면?
    int는 32비트, long long은 64비트까지 사용할 수 있지만, 플래그가 70개, 100개를 넘어가면 조금 부담됩니다. 그럴 때는 비트마스크보다도 다른 자료구조(예: std::bitset)를 쓰거나, 구조체나 클래스로 나눠서 관리하는 편이 낫습니다.


5. 주의할 점

  • 가독성: 비트 연산 자체는 간단하지만, 플래그가 너무 많으면 “어떤 비트가 뭘 의미하는지” 헷갈리기 쉽습니다. 변수 이름, 주석, 혹은 enum 등을 통해 의미를 명확히 해주세요.
  • 오류 발생 시 디버깅: 예를 들어 “5번째 비트가 켜진 상태였어야 하는데 안 켜졌다” 같은 디버깅은 꽤 복잡할 수 있어요. 따라서 충분한 로그, 디버깅 툴을 활용합시다.
  • 타입 크기: int인지 long long인지, 또는 std::bitset인지 미리 정해두고 일관성 있게 쓰는 게 좋아요.

마무리

C++에서 BitmaskBit Flags는 여러 상태를 한 번에 관리하기에 정말 편리한 도구입니다. 공간 절약부터 빠른 연산까지 장점이 많아요. 다만 플래그가 너무 많으면 오히려 헷갈릴 수 있으니, 적절한 선에서 사용하고, 가독성을 위해 enum class나 오버로딩 같은 기법도 잘 활용하면 좋겠죠.

이 포스팅이 도움이 되셨길 바랍니다. 궁금한 점이나 다른 의견이 있다면 언제든 댓글 남겨주세요!

profile
그래픽스, 수학, 물리, 게임 만세

0개의 댓글