C & C++/디딤돌 C언어

Part 10. 비트 연산자와 쉬프트 연산자

언휴 2024. 1. 9. 08:55

 

C언어 - 비트 연산자, 쉬프트 연산자
00:19 비트 연산자
01:22 [실습] 비트 연산자
02:44 xor 연산을 이용한 대칭형 암호화
03:28 [실습] xor 연산을 이용한 대칭형 암호화
05:46 쉬프트 연산자
07:00 [실습] 쉬프트 연산

32. 비트 연산자

 

&
|
^
~

C언어에서는 비트 단위로 논리 연산을 수행하는 비트 연산을 제공하고 있어요.
비트 연산의 피연산자는 정수 형식이 올 수 있어요.

이항 연산자 &는 비트 단위로 논리곱 연산을 수행하죠.
예를 들어 6&5를 하면 6은 이진수로 110이고 5는 이진수로 101이므로 6&5의 연산 결과는 이진수 100이고 10진수 4예요.

6&5
6&5

이항 연산자 | 는 비트 단위로 논리합 연산을 수행하죠.
예를 들어 6|5의 연산 결과는 이진수 111 이고 10진수 7이예요.

논리합
논리합

이항 연산자 ^는 비트 단위로 상호 배타 논리 연산을 수행하죠.
즉 같은 자리의 비트가 서로 다르면 1, 같으면 0 이예요.
예를 들어 6^5의 연산 결과는 이진수 011 이고 10진수 3이예요.

6^5
6^5

단항 연산자 ~는 1인 자리는 0으로 0인 자리는 1로 바꾸는 연산을 수행합니다.

◈ 비트 연산

#include <stdio.h>
int main()
{
    int i = 0x11FF0000;
 
    printf("6&5: %d \n",6&5);
    printf("6|5: %d \n",6|5);
    printf("6^5: %d \n",6^5);
    printf("~11FF0000 : %X \n", ~i); //쉽게 확인할 수 있게 16진수로 표현
    return 0;
}

◈ 실행 결과

6&5: 4
6|5: 7
6^5: 3
~11FF0000 : EE00FFFF

◈ 기본연습
다음 코드를 실행하였을 때 출력 결과를 예측하고 이유를 설명하세요.

#include <stdio.h>
int main()
{
    int a = 0x12345678;
    int b = 0xFFFF0000;
    int c = 0x0000FFFF;
    printf("%#X\n",a&b);//16진수로 출력하고 앞에 0X도 표시
    printf("%#X\n",a&c);
    return 0;
}
더보기

답:
0X12340000
0X5678
▷ 이유
& 는 비트 단위의 AND 연산입니다.
0xFFFF0000은 이진수로1111 1111 1111 1111 0000 0000 0000 0000 입니다.
따라서 16진수 상위 4자리는 그대로 남고 하위 4자리는 모두 0으로 변합니다.
따라서 a&b결과는 0x12340000입니다.
0x0000FFFF는 이진수로 0000 0000 0000 0000 1111 1111 1111 1111 입니다.
따라서 16진수 상위 4자리는 0으로 변하고 하위 4자리는 그대로 남습니다.
따라서 a&c 결과는 0x00005678이며 출력에서 앞에 0은 출력하지 않아 0x5678을 출력합니다.

33. 비트 연산자 ^를 이용한 암호화

어떤 수에 같은 수로 ^ 연산을 두 번 하면 원래 수가 되는 특징이 있어요.
이를 이용하면 간단한 암호화와 복호화를 할 수 있죠.
이처럼 같은 키를 암호화와 복호화에 사용하는 것을 대칭형 암호화라 불러요.

비트 연산자 ^를 이용한 암호화및 복호화
비트 연산자 ^를 이용한 암호화및 복호화

◈ 간단한 대칭형 암호화

#include <stdio.h>
int main()
{
    int original = 0x12345678; //원본 데이터
    int cryptograph = 0;  //암호화 데이터
    int decrypt = 0;        //복화화 데이터
    int key = 0x394A38C9;       //암호화와 복호화에 사용할 키
    printf("원본 데이터: %#X \n",original);
    cryptograph = original ^ key; //암호화
    printf("암호화 데이터: %#X \n",cryptograph);
    decrypt = cryptograph ^ key; //복호화
    printf("복호화 데이터: %#X \n",decrypt);
    return 0;
}

◈ 실행 결과

원본 데이터: 0X12345678
암호화 데이터: 0X2B7E6EB1
복호화 데이터: 0X12345678

◈ 기본 연습
다음 프로그램의 출력 결과를 예측하시오.

#include <stdio.h>
int main()
{
    int a = 0x12345678;
    int b = 0x456789AB;
    int c = 0; 
    int d = 0; 
    c = a^b;
    printf("%#X \n", c);
    d = c^b;
    printf("%#X \n", d); 
    return 0;
}
답:
0X5753DFD3
0X12345678
특정 값을 우측 피연산자로 ^연산을 두 번하면 원래의 값이 만들어집니다.
이러한 이유로 a^b로 암호한 데이터 c를 c^b를 하였을 때 원래 값인 a와 같아지는 것입니다.

34. 쉬프트 연산자

<<
>>

쉬프트 연산은 좌항에 있는 피연산자를 우항에 있는 수만큼 비트 자리 이동하는 연산을 수행해요.
<< 는 왼쪽 쉬프트 연산자이고 >> 는 오른쪽 쉬프트 연산자죠.

왼쪽 쉬프트 연산을 하면 좌항에 있는 피연산자의 값이 우항에 있는 수만큼 왼쪽으로 자리 이동하고 빈 자리는 0으로 채우죠.

만약 부호없는 수 3을 왼쪽으로 4자리 이동시키면 48이예요.
이진수로 생각해 보세요.
이진수 011(10진수 3)을 왼쪽으로 4자리 이동하면 이진수 0110000(10진수 48)이 되죠.
이것을 다시 16진수로 얘기하면 16진수 3(10진수 3)을 왼쪽으로 4자리 이동하면 16진수 30(10진수 48)이예요.
참고로 16진수의 한자리는 2진수 4자리를 차지하죠.

unsigned 형식의 왼쪽 쉬프트 연산
unsigned 형식의 왼쪽 쉬프트 연산

그리고 부호있는 수 -3을 왼쪽으로 4자리 이동시키면 -48이예요.

부호있는 수를 왼쪽 쉬프트 연산
부호있는 수를 왼쪽 쉬프트 연산

왼쪽 쉬프트 연산 수행 결과를 보면 왼쪽으로 한 자리 이동할 때마다 곱하기 2한 결과와 같아요.
3을 왼쪽으로 4자리를 이동하면 3에 2를 4번 곱한 결과인 48이예요.

◈ 왼쪽 쉬프트 연산 예

#include <stdio.h>
int main()
{
    unsigned u1 = 3, u2 = 0;
    int i1 = -3, i2 = 0;
    u2 = u1 << 4;
    printf("16진수 %X %X \n",u1, u2); //확인하기 쉽게 16진수로 출력
    printf("10진수 %u %u \n",u1, u2); //부호없는 10진수로 출력
    i2 = i1 << 4;
    printf("16진수 %X %X \n",i1, i2); //확인하기 쉽게 16진수로 출력
    printf("10진수 %d %d \n",i1, i2); //10진수로 출력
    return 0;
}

◈ 실행 결과

16진수 3 30
10진수 3 48
16진수 FFFFFFFD FFFFFFD0
10진수 -3 -48

오른쪽 쉬프트 연산은 좌항에 오는 피연산자가 부호 없는 수와 부호 있는 수일 때 차이가 있어요.
부호 없는 수가 피 연산자로 오면 빈 자리에 0이 채워져요.
하지만 부호 있는 수가 피 연산자로 오면 부호 비트는 자리 이동하지 않고 빈 자리에 부호 비트가 채워지죠.
그렇지만 두 가지 모두 오른쪽 쉬프트 연산은 자리 이동하는 만큼 2로 나눈 결과와 같아요.

오른쪽 쉬프트 연산
오른쪽 쉬프트 연산

◈ 오른쪽 쉬프트 연산 예

#include <stdio.h>
int main()
{
    unsigned u1 = 48, u2 = 0;
    int i1 = -48, i2 = 0;
    u2 = u1 >> 4;
    printf("16진수 %X %X 10진수: %u %u \n",u1, u2, u1, u2); 
    i2 = i1>>4;
    printf("16진수 %X %X 10진수: %d %d\n",i1, i2, i1, i2);
    return 0;
}

◈ 실행 결과

16진수 30 30 10진수 48 3
16진수 FFFFFFD0 FFFFFFFD 10진수 -48 -3

◈ 기본 연습

1. 다음 프로그램의 출력 결과를 예측하시오.

#include <stdio.h>
int main()
{
    unsigned u = 5;
    int i = -5;
    printf("%d \n",u<<3);
    printf("%d \n",i<<3); 
    return 0;
}
더보기

답:
40
-40
<< 연산을 한 번 하면 2를 곱한 효과를 갖습니다.
따라서 3자리 왼쪽으로 이동하였으므로 2의 3승인 8을 곱한 효과를 갖습니다.

2. 다음 프로그램의 출력 결과를 예측하고 이유를 설명하시오.

참고:
unsigned int 형식의 0x80000000은 2147483648
int 형식의 0x80000000은 -2147483648

#include <stdio.h>
int main()
{
    unsigned u1 = 100;
    unsigned u2 = 0x80000000;
    int i1 = 100;
    int i2 = 0x80000000;
    printf("%d \n",u1>>3);
    printf("%d \n",i1>>3);
 
    printf("%d %#X\n",u2>>3, u2>>3);
    printf("%d %#X\n",i2>>3, i2>>3 ); 
 
    return 0;
}
더보기

답:
12
12
268435456 0x10000000
-268435456 0xF0000000
>> 연산을 한 번 하면 2로 나눈 효과를 갖습니다.
따라서 3자리 오른쪽으로 이동하였으므로 2의 3승인 8로 나눈 효과를 갖습니다.