Quick Syntax
비트 연산은 숫자를 계산하기보다 비트 패턴을 켜고, 끄고, 검사하는 데 씁니다. 플래그와 mask는 처음부터 unsigned 타입과 유효한 bit index를 함께 정하는 편이 안전합니다.
enum {
FLAG_READ = 1u << 0,
FLAG_WRITE = 1u << 1,
FLAG_EXEC = 1u << 2
};
unsigned flags = 0u;
flags |= FLAG_READ; // set
flags &= ~FLAG_WRITE; // clear
flags ^= FLAG_EXEC; // toggle
if ((flags & FLAG_READ) != 0u) {
/* FLAG_READ가 켜져 있음 */
}읽을 때는 flags & mask, 켤 때는 |=, 끌 때는 &= ~, 뒤집을 때는 ^=를 씁니다. shift 횟수는 타입 폭보다 작아야 하며, signed 값의 shift는 플래그 표현에 쓰지 않습니다.
mask와 shift
1u << n은 n번째 비트를 켠 mask를 만드는 기본 형태지만, n은 unsigned의 비트 폭보다 작아야 합니다. 입력값을 바로 shift count로 쓰지 말고 먼저 검사합니다.
#include <limits.h>
#include <stddef.h>
int make_mask(unsigned bit, unsigned *out) {
if (out == NULL || bit >= sizeof(unsigned) * CHAR_BIT) {
return 0;
}
*out = 1u << bit;
return 1;
}shift count가 음수이거나 피연산자 폭 이상이면 정의되지 않은 동작입니다. bitmask, flags, 레지스터처럼 비트 패턴이 목적이면 signed 값을 shift하지 말고 unsigned 타입으로 시작합니다.
고정 폭 프로토콜이나 레지스터는 uint32_t 같은 타입을 쓸 수 있는 환경인지 확인하고, 그 폭에 맞는 상수를 사용합니다.
식과 경계
비트 연산과 비교를 섞을 때는 괄호가 필수에 가깝습니다.
enum { FLAG_READ = 1u << 0 };
unsigned flags = 0u;
int wrong = flags & FLAG_READ == 0; // flags & (FLAG_READ == 0)
int disabled = ((flags & FLAG_READ) == 0u);~mask는 mask의 비트만 뒤집는 연산이 아니라, 해당 unsigned 타입의 모든 비트를 뒤집습니다. 그래서 clear 패턴은 mask와 flags가 같은 타입·폭이라는 전제에서 사용합니다.
주의할 점
&는 비트 AND이고&&는 논리 AND입니다. 플래그 검사는 보통(flags & mask) != 0형태로 씁니다.- signed 음수의 shift와 부호 비트를 이용한 mask는 이식성 판단을 어렵게 만듭니다.
1 << bit대신1u << bit로 unsigned mask임을 드러냅니다.- 외부 입력에서 온 bit index, mask, 타입 폭은 검사 없이 신뢰하지 않습니다.
참고 링크
2 sources