비트 연산은 값을 계산한다기보다 비트 패턴을 켜고 끄고 검사하는 도구에 가깝습니다.
- 플래그를 켜고 끄고 검사하는 패턴을 빠르게 다시 보고 싶다
<<와>>를 산술처럼 읽어도 되는지 확인하고 싶다- signed 타입에 비트 연산을 섞을 때 어디서 위험해지는지 정리하고 싶다
숏컷 코드
flags |= BIT; // set
flags &= ~BIT; // clear
flags ^= BIT; // toggle
if (flags & BIT) { }문법
비트 연산은 특정 자리에만 영향을 주는 마스크를 먼저 만든 뒤, 그 마스크로 값을 조작하는 방식으로 읽으면 가장 쉽습니다.
enum {
FLAG_READ = 1u << 0,
FLAG_WRITE = 1u << 1,
FLAG_EXEC = 1u << 2
};
unsigned int perms = 0;
perms |= FLAG_READ;
perms |= FLAG_WRITE;
perms &= ~FLAG_WRITE;
if (perms & FLAG_READ) {
printf("read enabled\n");
}1u << n은 "2의 n제곱"보다 "n번째 비트를 켠 마스크"로 보는 편이 실전적입니다.
if ((perms & FLAG_READ) != 0u) {
printf("read enabled\n");
}비트 검사에서는 "0이 아닌가"까지 같이 읽는 편이 조건 의미가 더 분명합니다.
경계 이해
왼쪽 shift는 비트 위치를 만들거나 비트 패턴을 밀 때 자주 씁니다.
unsigned int bit0 = 1u << 0; // 0001
unsigned int bit1 = 1u << 1; // 0010
unsigned int bit2 = 1u << 2; // 0100반대로 signed 값을 오른쪽 shift하기 시작하면 부호 비트 해석이 섞여 의미가 흐려질 수 있습니다.
int x = -8;
int y = x >> 1; // 구현 의존적인 결과 가능그래서 bitmask, flags, 레지스터 같은 코드는 unsigned를 기본형으로 두는 편이 안전합니다.
반대로 이런 식은 읽기 어렵습니다.
if (flags & FLAG_READ == 0) { }우선순위 때문에 의도와 다르게 읽히기 쉬우므로 괄호를 같이 두는 편이 낫습니다.
if ((flags & FLAG_READ) == 0u) { }체크포인트
|는 특정 비트를 켭니다.& ~mask는 특정 비트를 끕니다.^는 토글입니다.& mask는 켜져 있는지 검사할 때 씁니다.1u << n은 n번째 비트 마스크를 만드는 기본 패턴입니다.- 비트 패턴 조작은
unsigned타입을 기본으로 보는 편이 안전합니다.
주의할 점
비트 연산은 표현식은 짧아도 signed 타입과 섞이는 순간 의미가 급격히 흐려집니다. "비트 패턴을 조작한다"는 목적이 분명하면 처음부터 unsigned로 고정하고, 산술과 비트 조작을 같은 식에서 섞지 않는 편이 좋습니다.
참고 링크
1 sources