꾸꾸리

4-3. 비트 연산자 본문

Programming Language/C

4-3. 비트 연산자

O773H 2018. 7. 22. 18:29
728x90

1-0. 들어가기 전에


비트 단위로 연산을 진행하는 비트 연산자는 주로 하드웨어 관련 프로그래밍에 활용되지만, 그 이외의 영역에서도 사용되어 메모리 공간의 효율성을 높이고 연산의 주를 줄이는 요인이 되기도 한다. 그런데 이 연산자의 활용적 측면을 지금 언급하면 이해하는데 많은 부담이 있으므로, 여기서는 일단 연산자의 기능을 이해하는데 초점을 맞추자.


1-1. 비트 연산자의 종류


연산자 

연산자의 기능 

결합방향 

 &

 비트단위로 AND 연산을 한다.

-> 

 |

 비트단위로 OR 연산을 한다. 

-> 

 ^

 비트단위로 XOR 연산을 한다. 

-> 

 ~

 단항 연산자로서 피연산자의 모든 비트를 반전시킨다. 

<- 

 <<

 피연산자의 비트 열을 왼쪽으로 이동시킨다. 

-> 

 >>

 피연산자의 비트 열을 오른쪽으로 이동시킨다. 

-> 


위의 표에서 << 연산자와  >> 연산자는 '비트 이동(shift) 연산자'라 해서 비트 연산자와는 성향이 조금 다르다. 하지만 흔히 비트 연산자의 범주에 포함시켜서 이야기하기 때문에 함께 정리했다.


1-2. &연산자 : 비트단위 AND


&연산은 두 개의 비트가 모두 1일 때 1을 반환하는 연산이다.

0 & 0         -> 0을 반환

1 & 0         -> 0을 반환

0 & 1         -> 0을 반환

1 & 1         -> 1을 반환


다음의 예제를 확인해보자.


1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main(void)
{
    int num1 = 15;        // 00000000 00000000 00000000 00001111
    int num2 = 20;        // 00000000 00000000 00000000 00010100
    int num3 = num1 & num2;
    printf("AND 연산의 결과 : %d\n", num3);
    return 0;
}
cs


<실행 결과>

AND 연산의 결과 : 4


int형 변수의 크기는 일반적으로 4바이트이기때문에 4바이트를 기준으로 설명하겠다.

위 예제 7행을 살펴보자.


             00000000 00000000 000000000 00001111

&연산     00000000 00000000 000000000 00010100

----------------------------------------------------------------

             00000000 00000000 000000000 00000100


위에서 볼 수 있듯이, num1과 num2를 대상으로 &연산을 하게 되면, 같은 위치에 저장된 비트들간 & 연산이 진행되어 정수 4가 반환된다.


1-3. | 연산자 : 비트단위 OR


| 연산은 두 개의 비트 중 하나라도 1이면 1을 반환하는 연산이다.

0 | 0         -> 0을 반환

1 | 0         -> 1을 반환

0 | 1         -> 1을 반환

1 | 1         -> 1을 반환


& 연산자와 마찬가지로 예제를 통해 확인하자.


1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main(void)
{
    int num1 = 15;        // 00000000 00000000 00000000 00001111
    int num2 = 20;        // 00000000 00000000 00000000 00010100
    int num3 = num1 | num2;
    printf("OR 연산의 결과 : %d\n", num3);
    return 0;
}
cs


<실행 결과>

OR 연산의 결과 : 31


위 예제 7행을 살펴보면 다음과 같다.


              00000000 00000000 000000000 00001111

|연산         00000000 00000000 000000000 00010100

-----------------------------------------------------------------

              00000000 00000000 000000000 00011111



1-4. ^ 연산자 : 비트단위 XOR 


^ 연산은 두 개의 비트가 서로 다른 경우에 1을 반환하는 연산이다.


0 ^ 0         -> 0을 반환

1 ^ 0         -> 1을 반환

0 ^ 1         -> 1을 반환

1 ^ 1         -> 0을 반환


마찬가지로 예시를 통해 살펴보자.


1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main(void)
{
    int num1 = 15;        // 00000000 00000000 00000000 00001111
    int num2 = 20;        // 00000000 00000000 00000000 00010100
    int num3 = num1 ^ num2;
    printf("XOR 연산의 결과 : %d\n", num3);
    return 0;
}
cs


<실행 결과>

XOR 연산의 결과 : 27


위 예제 7행을 살펴보면 다음과 같다.


              00000000 00000000 000000000 00001111

^연산        00000000 00000000 000000000 00010100

----------------------------------------------------------------

              00000000 00000000 000000000 00011011


1-5. ~ 연산자 : 비트단위 NOT


~ 연산은 비트를 0에서 1로, 1에서 0으로 반전시키기 때문에 보수연산이라고도 불린다.


~0        -> 1을 반환

~1        -> 0을 반환


아래 예제를 확인해보자.


1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main(void)
{
    int num1 = 15;        // 00000000 00000000 00000000 00001111
    int num2 = ~num1;
    printf("NOT 연산의 결과 : %d\n", num2);
    return 0;
}
cs


<실행결과>

NOT 연산의 결과 : -16


위 예제의 6행을 살펴보면 다음과 같다.


11111111 11111111 11111111 11110000


이는 num1에 저장된 데이터의 모든 비트를 반전시킨 결과로, MSB 역시 반전되었다. 따라서 이 값은 음수이다.


[4-2. 정수와 실수의 표현방식] 부분에서 음의 정수 표현방법에 대해 알아봤다.

2의 보수를 이용해 양의 정수를 음의 정수로 바꿨는데, 이를 역으로 이용해서 위 데이터의 크기를 계산해보자.


양의 정수를 이용해 음의 정수를 구할 때 1의 보수를 취하고 1을 더하는 과정(2의 보수)을 거쳤다.

그렇다면, 음의 정수의 크기를 구하기 위해 우리는 이와 반대의 과정을 하면 됨을 알 수 있다.


11111111 11111111 11111111 11110000

------------------①1을 뺀다 --------------------

11111111 11111111 11111111 11101111

------------②1의 보수를 취한다----------------

00000000 00000000 00000000 00010000


이진수 10000 은 +16 이므로 위 예제에서 num2가 -16임을 알 수 있다.


1-6. << 연산자 : 비트의 왼쪽 이동(Shift)


<< 연산자는 두 개의 피연산자를 요구하며 다음의 의미를 갖는다.


* num1 << num2     num1의 비트 열을 num2칸씩 왼쪽으로 이동시킨 결과를 반환

* 8 << 2                정수 8의 비트 열을 2칸씩 왼쪽으로 이동시킨 결과를 반환


아래 예제를 통해 << 연산의 결과를 확인해보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
int main(void)
{
    int num = 15;        // 00000000 00000000 00000000 00001111
    
    int result1 = num << 1;        // num의 비트 열을 왼쪽으로 1칸씩 이동
    int result2 = num << 2;        // num의 비트 열을 왼쪽으로 2칸씩 이동
    int result3 = num << 3;        // num의 비트 열을 왼쪽으로 3칸씩 이동
 
    printf("1칸 이동 결과 : %d\n", result1);
    printf("2칸 이동 결과 : %d\n", result2);
    printf("3칸 이동 결과 : %d\n", result3);
    return 0;
}
cs


<실행 결과>

1칸 이동 결과 : 30

2칸 이동 결과 : 60

3칸 이동 결과 : 120


위 예제 7~9행을 살펴보면, 변수 num에 저장된 값의 비트 열을 왼쪽으로 각각 1, 2, 3 칸씩 이동시키고 있다.


5행(변수 num)  : 00000000 00000000 00000000 00001111 


7행          : 00000000 00000000 00000000 00011110    // 왼쪽으로 1칸 이동 ( 값 : 30 )

8행          : 00000000 00000000 00000000 00111100    // 왼쪽으로 2칸 이동 ( 값 : 60 )

9행          : 00000000 00000000 00000000 01111000    // 왼쪽으로 3칸 이동 ( 값 : 120 )


위에서 볼 수 있듯이 비트의 이동으로 인해 생기는 오른쪽 빈칸은 0으로 채워지고, 이동으로 인해 밀려나는 왼쪽 비트들은 그냥 버려진다. (비록 그 값이 1이여도 버려진다.) 우리는 위 예제를 통해 다음과 같은 사실을 알 수 있다.


"비트의 열을 왼쪽으로 1칸씩 이동시킬 때마다 정수의 값이 두 배가 된다."


그리고 위 내용을 토대로 다음 사실을 알 수 있다.


"비트의 열을 오른쪽으로 1칸씩 이동시킬 때마다 정수의 값이 2로 나누어진다."


이 사실을 기억하면, 상황에 따라 곱셈과 나눗셈 연산은 비트의 이동 연산으로 대체할 수 있으며, 이는 성능의 향상으로 이어진다.

CPU 입장에서는 곱셈과 나눗셈이 비트의 이동보다 부담스러운 연산이기 때문이다.


1-7. >> 연산자 : 비트의 오른쪽 이동(Shift)


>> 연산자와 << 연산자의 가장 큰 차이점은 비트의 열을 이동시키는 방향에 있다. 음수의 경우만 한번 살펴보자.

예를 들어, -16의 경우를 살펴보면, 이를 나타내는 비트 열은 다음과 같다.


11111111 11111111 11111111 11110000            :         -16


이것을 >> 연산을 통해 오른쪽으로 세 칸 이동시키면 어떻게 될까?

이는 CPU에 따라 결과가 달라지는데, 음의 값을 유지하기 위해 1을 채우는 CPU도 있고, 음의 값 유지에 상관하지 않고 0을 채우는 CPU도 있다.

따라서 다음과 같은 결과를 얻을 수 있다.


00011111 11111111 11111111 11111110        // 0이 채워진 경우


11111111 11111111 11111111 11111110        // 1이 채워진 경우




728x90