c 비트연산잔 응용

떵떵·2022년 6월 8일
0

시프트 연산과 2의 거듭제곱

시프트 연산자는 2의 거듭제곱은 숫자를 빠르게 구할 때 유용하다.

int main()
{
	unsigned char num1 = 1;
    printf("%d\n", num1 << 1);
    printf("%d\n", num1 << 2);
    printf("%d\n", num1 << 3);
}
>>> 2
>>> 4
>>> 8

왼쪽으로 이동하는 << 연산자는 한 번씩 이동하면 2의 거듭제곱으로 수가 늘어난다. 즉, 비트의 각 자릿수는 2의 거듭제곱을 뜻하므로 비트의 이동횟수는 지수라 할 수 있다.

첫째자리 마지막자리 넘어가면?

int main()
{
	unsigned char num1 = 240; // 1111 0000
    unsigned char num2 = 15; // 0000 1111
    
    unsigned char num3, num4;
    
    num3 = num1 << 2; // 1100 0000
    num4 = num2 >> 2; // 0000 0011
}
>>> 192
>>> 3

unsigned char은 1바이트 이므로 8비트까지만 존재한다. 이떄 마지막자리와 첫째자리를 넘어선다면 그대로 사라지며 남은 비트만 출력된다.

240 이었던 num1은 1100 0000이 되면서 192가 되고
15였던 num2는 0000 0011이 되면서 3이 된다.

비트에서 첫 번째 비트를 최상위 비트(Most Significant Bit, MSB)이고
비트에서 마지막 비트를 최하위 비트(Least Significant Bit, LSB)이다.

부호있는 자료형 비트 연산

unsigned로 부호없는 비트로만 예를 들었지만 부호있는 빈트를 비트 연산할 때는 부호비트를 조심해야한다.

부호가 있는 비트의 최상위 비트는 부호 비트로서 -128(1000 0000)이다.
부호 비트를 제외한 비트들은 양수로서 부호비트가 1일때 양수 비트들이 있다면 -128 + 1인 비트 수로 계산된다.

부호가 있는 비트의 비트가 1000 0011이면 -128 + 2 + 1 = -125가 되는 것이다.

부호비트 1의 >> 연산

부호가 있는 비트의 부호비트가 1일때 >> 연산자를 사용한다면 오른쪽으로 이동하는 칸만큼 1이 생성된다

ex) 1000 0000 >> 2 = 1110 0000 으로 -128 + 64 + 32 = -32가 된다.

int main()
{
	unsigned char num1 = 131; 1000 0011
    char num2 = -125 // 1000 0011
    
    unsigned char num3;
    char num4;
    
    num3 = num1 >> 5;
    num4 = num2 >> 5;
    
    printf("%u\n", num1);
    printf("%d\n", num2);
}
>>> 4
>>> -4

부호가 있는 비트가 1000 0011일때 >> 5를 하였기 때문에 1111 1100이 되고 -128 + 64 + 32 + 16 + 8 + 4 = -4가 되어 -4가 출력 된 것이다.

부호비트 0의 >> 연산

부호 비트가 0일 때는 >>을 하여도 모자라는 공간이 0으로 채워지기 떄문에 부호 없는 비트와 동일하다.

int main()
{
	char num1 = 67 // 0100 0011
    char num2;
    num2 = num1 >> 5; // 0000 0010
    printf("%d\n", num2);
}

부호 비트가 0이기 때문에 모자라는 공간이 0으로 채워져서 0100 0011 -> 0000 0010이 되어 2가 출력된다.

부호비트 1의 << 연산

부호 비트가 1일 때 >>연산자를 하면 모자라는 공간이 1로 채워진다. 하지만 <<으로 이동하면 어떻게 될까?

int main()
{ 	
	char num1 = 113; // 0111 0001
    char num2 = -15; // 1111 0001
    char num3, num4, num5, num6;
    
    num3 = num1 << 2; // 1100 0100
    num4 = mum2 << 2; // 1100 0100
    
    num5 = num1 << 4; // 0001 0000
    num6 = num2 << 4; // 0001 0000
}
>>> -60
>>> -60
>>> 16
>>> 16

부호 비트가 0인 양수 0111 0001을 << 2를 하면 1이 부호 비트를 덮어쓰게 되면서 1100 0100이 되고, 양수였던 수가 음수가 된다. (= -60)

최대 양수를 넘어서 음수가 되는 것을 오버플로우(overflow)라고 지난 글에 썼을 것이다.

부호 비트가 1인 음수는 << 2를 하여도 똑같이 -60이다. 하지만 << 4로 이동시키면 1은 모두 사라지고 부호 비트에 0이 와서 양수가 된다.

1111 0001 = -15
<< 2
1100 0100 = -60
<< 2
0001 0000 = 16

이처럼 부호가 있는 비트의 연산은 이동할 때마다 부호 비트의 숫자에 따라 양수와 음수가 확확 바뀌기 떄문에 의도치 않은 결과가 나올 수 있다.

항상 부호 비트를 생각하면서 연산해야 된다.

flag 처리

플래그(flag)는 깃발에서 유래한 용어이다.

int main()
{
	unsigned char flag = 0; // 0000 0000으로 초기화
    
    flag |= 1; // 0000 0001 킴 
    flag |= 2; // 0000 0010 킴
    
    if (flag & 1)
    	printf("0000 0001이 켜져있음\n);
    else 
    	printf("0000 0001이 꺼져있음\n);
        
    if (flag & 2)
    	printf("0000 0001이 켜져있음\n);
    else 
    	printf("0000 0001이 꺼져있음\n)
}
>>> 0000 0001이 켜져있음
>>> 0000 0010이 켜져있음

flag 비트 켜기 |=

flag의 '비트 켜기 동작'은 비트OR연산의 특성을 활용한 것이다.
'flag |= 숫자' 에서 '|='은 비트 켜기, '숫자'는 '마스크'라고 하며 무슨 상태를 킬지 원하는 숫자를 넣는다.

flag - 0000 0000
--------OR--------
mask - 0000 0001

flag - 0000 0001 으로 1이 켜진다.

flag 비트 검사 &=

flag의 '비트 검사 동작'은 비트AND연산의 특성을 활용한 것이다.
'flag &= 숫자' 에서 '&='은 비트 검사, '숫자'는 검사할 비트의 10진수 이다.

floag가 0000 0101; 일때 6번째가 켜져있는지 검사하고싶다면 다음과 같이 하면 된다.

flag - 0000 0101
-------AND------
mask - 0000 0100

결과 - 0000 0100

flag를 mask로 &연산 시켰을떄 결과가 mask이면 해당 flag는 켜져있는 것이다.

flag 비트 종료 &= ~mask

int main()
{
	unsigned char flag = 3; // 0000 0011으로 초기화
    
    flag &= ~2; // 1111 1101 => 0000 0001
    
    if (flag & 1)
    	printf("0000 0001이 켜져있음\n);
    else 
    	printf("0000 0001이 꺼져있음\n);
        
    if (flag & 2) // 
    	printf("0000 0001이 켜져있음\n);
    else 
    	printf("0000 0001이 꺼져있음\n)
}
>>> 0000 0001이 켜져있음
>>> 0000 0010이 꺼져있음

'flag &= ~숫자'는 flag 비트 종료 동작이다. 끄고싶은 숫자로 '&= ~숫자'를 하면 해당하는 숫자가 반전되고 &비트 연산을 통해 꺼진다.

flag가 0000 0111(7) 일때 6번째 7번째를 끄고 싶으면 다음과 같다
flag &= ~6;
0000 0111 - flag
0000 0110 - mask
=> ~
0000 0111 - flag
1111 1001 - ~mask
=> &=
0000 0001

mask가 끄고싶은 숫자를 할당하고 반전(~)시키면 해당 숫자는 0이되고 모두 1로 바뀐다. flag와 &(and) 시킨다면 해당 숫자는 0이기 때문에 꺼지고 나머지는 반전으로 인해 1로 바껴서 &(and)시켜도 1으로 남기에 원하는 위치만 끌 수 있는 것이다.

flag 비트 토글 ^=

비트 토글이란 켜져있으면 끄고, 꺼져있으면 키는 것이다.

int main()
{
	unsigned char flag = 7; // 0000 0111으로 초기화
    
    flag ^= 2; // 0000 0001 킴 
    flag ^= 8; // 0000 0010 킴
    
    if (flag & 2)
    	printf("0000 0010이 켜져있음\n);
    else 
    	printf("0000 0010이 꺼져있음\n);
        
    if (flag & 8)
    	printf("0000 1000이 켜져있음\n);
    else 
    	printf("0000 1000이 꺼져있음\n)
}
>>> 0000 0001이 꺼져있음
>>> 0000 1000이 켜져있음

^(XOR)연산자는 다르면 1, 같으면 0을 반환한다.
flag가 0000 0111일때 ^= 2를 한다면
0000 0111
0000 0010
=> ^=
0000 0101 이 되면서 2가 꺼진다.

위와같이 안 켜져있는 5번째 비트는 토글(^=) 했을 떄 켜지는 것을 알 수 있다.

0000 0111
0000 1000
=> ^=
0000 1111 이 되면서 5번째(8)가 켜지는 것이다.

이번 내용들은 쓰이는 곳이 많지 않기 때문에 나중에 리눅스 커널의 소스 코드를 보거나 하드웨어를 다룰 때 플래그 처리 부분이 나오면 다시 보면 된다.

0개의 댓글