본문 바로가기
develop

C의 비트연산 겉핥기

by 찬이 2010. 8. 18.
비트연산에 대해서 간략히 살펴보겠습니다.
교과서적인 혹은 복사해온 듯한 내용들은 생략하고, 사용하는데 헷갈릴만한 몇가지를 짚어보겠습니다.

비트의 표현

C에서 비트를 위한 변수는 존재하지 않습니다. 상수와 변수는 눈에 보이는 것이라고 한다면, 비트는 값을 구성하는 눈에 보이지 않는 것이라 할 수 있습니다. 그렇기 때문에 char, int 형과 같은 데이터의 구성요소일뿐, 비트만을 위한 데이터형이 존재하는 것은 아닙니다.

비트의 연산자

C에서 비트와 관련된 연산은 '&', '|', '!', '~', '<<', '>>' 등이 있습니다.
아래의 숫자들은 2진수입니다.

'&' 는 AND 연산입니다. 둘다 1 이어야만 1이 됩니다.
0  &  0  ==  0
0  &  1  ==  0
1  &  0  ==  0
1  &  1  ==  1

'|' 는 OR 연산입니다. 둘다 0 이어야만 0이 됩니다.
0  |  0  ==  0
0  |  1  ==  1
1  |  0  ==  1
1  |  1  ==  1

'^' 는 XOR 연산입니다. 둘다 같으면 0, 다르면 1이 됩니다. 
0  ^  0  ==  0
0  ^  1  ==  1
1  ^  0  ==  1
1  ^  1  ==  0

'<<', '>>' 는 쉬프트 연산입니다. 연산자 우측의 숫자만큼 비트들을 옮겨줍니다.
새로 생기는 부분은 0으로 채워지고, 넘치는 부분은 사라집니다. 
0110  <<  1  ==  1100
0110  <<  2  ==  1000
0110  >>  1  ==  0011
0110  >>  2  ==  0001

'~' 는 1의 보수 연산입니다. 한글로 다른 말이 무엇인지 잘 생각이 안나네요.
0은 1로, 1은 0으로 변경됩니다.
~0110  ==  1001
~1001  ==  0110
~0000  ==  1111
~1111  ==  0000

비트 구하기

비트연산은 빠른 계산을 위해서 사용되기도 합니다만, 그보다는 한개의 데이터에 여러가지 flag를 의미하는 비트들의 상태를 구하거나 셋팅하기 위해서 많이 사용됩니다.

만약 어떤 함수를 호출할때 32가지의 상태값을 줄 수 있는데, 그 32가지가 1개 이상의 조합이 가능하다면 과연 얼마나 많은 경우의 수가 생길까요?
남자 or 여자, 내국인 or 외국인, 취업자 or 미취업자, 미혼 or 기혼, 자동차 소유 or 미소유 등과 같은 식이라면 갖가지 조합에 대해서 넘겨주어야 하는데, 함수에다 파라메터를 32가지를 할 수도 없는 노릇이고... 물론 struct 과 같은 구조체를 이용할 수도 있습니다만, 비트로 표현하면 4 bytes면 될 것을 구조체로 하게 되면 최소 32 bytes가 필요하겠지요.
그 데이터가 만개, 십만개가 된다면...?

이런 경우, 비트연산을 적극적으로 활용하게 되는데, 첫번째 비트가 0이면 남자, 두번째 비트가 0이면 내국인, 세번째 비트가 0이면 미취업자와 같은 식으로 쓰면 되겠지요.

int profile = 5;  // c에서는 bit 표현안되므로 10진수 입니다.
if ( profile & 1 ) printf( "여자\n" ); else printf( "남자\n" );
if ( profile & 2 ) printf( "외국인\n" ); else printf( "내국인\n" );
if ( profile & 4 ) printf( "취업자\n" ); else printf( "미취업자\n" );
....

실행결과>

여자
내국인
취업자

profile = 5 는 비트로 0101 입니다. (비트라는 의미로 b를 붙여서 0101b로 표기하기도 합니다)
(profile & 1)의 경우를 풀어보면, (0101 & 0001)이 되므로 계산값은 1 (TRUE)입니다.
그래서 "여자\n"가 출력됩니다.
(profile & 2)의 경우를 풀어보면, (0101 & 0010)이 되므로 계산값은 0 (FALSE)입니다.
그래서 "내국인\n"이 출력됩니다.
(profile & 4)의 경우를 풀어보면, (0101 & 0100)이 되므로 계산값은 4 (TRUE)입니다.
그래서 "취업자\n"가 출력됩니다.

실제 프로그래밍 작업에서는 선택사항이 상당히 많고 헷갈리기 쉽기 때문에, 아래와 같은 식으로 사용합니다.

typedef enum {
    FEMALE_FLAG,
    FOREIGNER_FLAG,
    EMPLOEER_FLAG,
};

typedef enum {
    FEMALE_MASK = 1 << FEMALE_FLAG,
    FOREIGNER_MASK = 1 << FOREIGNER_FLAG,
    EMPLOEER_MASK = 1 << EMPLOEER_FLAG,
};

if( profile & FEMALE_MASK ) ....
if( profile & FOREIGNER_MASK ) ....
if( profile & EMPLOEER_MASK ) ....

방식은 별다르지 않지만, 비트연산 특성상 10진수나 16진수로 적어주다보면 1비트가 아니라 여러개의 비트가 들어가는 경우가 생기기도 합니다. 이럴때 분석이 쉽지가 않기 때문에 위와 같이 적어주게 되면 그런 실수를 막을 수가 있습니다.

비트의 변경

위에서 비트값을 가져오는 것을 사용했다면, profile과 같은 변수의 값을 변경해주는 작업도 있을 수 있겠지요.
비트를 추가해줄때는 늘어나는 느낌의 OR('|') 연산, 비트를 제거해줄때는 조건이 까다로운 느낌의 AND('&') 연산이라고 생각하시면 좀 쉽지 않을까 생각해봅니다.

int profile = ( FEMALE_MASK | EMPLOEER_MASK ); // 5 와 동일합니다
if ( profile & FOREIGNER_MASK ) printf( "외국인\n" ); else printf( "내국인\n" );

profile |= FOREIGNER_MASK;
if ( profile & FOREIGNER_MASK ) printf( "외국인\n" ); else printf( "내국인\n" );

profile &= ~FOREIGNER_MASK;
if ( profile & FOREIGNER_MASK ) printf( "외국인\n" ); else printf( "내국인\n" );

실행결과>

내국인
외국인
내국인

첫번째는 "내국인"이 출력되는 건 이해가 가시겠지요? 윗 부분에서 했던 것과 같은 것이라서 기억해내셔야 합니다!!

두번째는 FOREIGNER_MASK를 더해줬으니, ( 0101 | 0010 ) 이 되는 겁니다. 그래서 FOREIGNER_MASK에 해당하는 우측 두번째 비트가 1로 변경될테니, "외국인"이 출력되는게 맞겠지요?

세번째는 '~'연산자를 사용했습니다. 특정한 비트를 제거할 목적으로 반전된 값을 구하기 위해서 주로 사용됩니다.
FOREIGNER_MASK가 0010 이었습니다. 우측 두번째 비트를 무조건 없애려면, 우측 두번째 비트만 0으로 해서 '&' 해주게 되면 그 부분만 0으로 변하겠지요?
그래서 ~FOREIGNER_MASK는 1101 값이 되기 때문에, 결국은 ( 0111 & 1101 ) 이 되는 겁니다. 계산해보면 0101 이 됩니다. 따라서, "내국인"이 출력되는 것입니다.

마치며...

간단하고 알기 쉽게 적는다는 것은 역시 쉽지가 않군요.
제가 잘못 적었거나, 내용이 부족하거나 혹은 헷갈리는 내용이 있다면, 알려주시면 업데이트 하도록 하겠습니다.

'develop' 카테고리의 다른 글

memcpy()  (0) 2010.08.23
삼성 오픈소스 사이트 소개  (0) 2010.08.19
ASCII Character Code Table  (0) 2010.08.12
Ubuntu (우분투) ISO 다운받기  (1) 2010.08.12
strtok()  (0) 2010.07.14

댓글