평생 공부하는 빠타박스 블로그 : Learning is Happiness
article thumbnail
SMALL

- "본 내용은 Inflearn에 Rookies 강사님의 강의로 학습하고 있는 내용입니다."-


>>>>> 이전 내용 : 비교연산과 논리연산


비트 연산과 비트 플래그

전체코드

더보기
/*비트 연산과 비트플래그*/
#include <iostream>
using namespace std;

// 주제 : 데이터 연산

// 비트 플래그
unsigned char flag; // 부호를 없애야 >> 하더라도 부호비트가 딸려오지 않는다.

int main()
{
#pragma region 비트연산
	// 언제 필요한가? (많이 없긴하다)
	// 종종 비트 단위의 조작이 필요할 때
	// 게임서버 ID를 만들거나. 꾸겨서 넣다보니 비트 조작이 필요
	// - 대표적으로 BitFlag

	// 비트연산자 어떤 숫자에 대해 비트단위로 세밀하게 조작할 때.
	// 특정 비트를 끄거나 켜거나 하는

	// ~ bitwise not
	// : 단일 숫자의 모든 비트를 대상으로 0은 1, 1은 0으로 (비트를 뒤집어준다)

	// & bitwise and
	// : 두 숫자의 모든 비트 쌍을 대상으로, (즉 두 비트 사이에 값으로 판단 1 0 = 0, 1 1 = 1 같은 식)

	// | bitwise or
	// : 두 숫자 사이에 or문 ( 0 0 = 0, 1 0 = 1, 1 1 = 1 과 같은형태)

	// ^ bitwise xor
	// : 두 숫자의 모든 비트 쌍을 대상으로 xor를 한다 ( 0 0 = 0 , 1 0 = 1, 1 1 = 0, 같으면 싫다 같은)
	// 숫자를 두번 xor 하면 원복된다 (암호화 할 때 유용?)

	// << 비트 좌측 이동
	// : 비트열을 N만큼 왼쪽으로 이동
	// 왼쪽의 넘치는 N개의 비트는 버린다. 새로 생성되는 N개의
	// *2를 할 때 자주 보이는 패턴 (비트당 하나씩 2의 제곱 되기 때문)

	// >> 비트 우측 이동
	// : 좌측보다 까다로움 : 비트열을 N만큼 오른쪽으로 이동
	// 오른쪽에 넘치는 N개의 비트는 버린다.
	// 왼쪽 생성되는 N개 비트는? 부호가 있냐 없느냐에 따라 차이가 난다.
	// - 부호 비트가 존재할 경우 부호 비트를 따라간다 (부호 있는 정수라면 이 부분을 유의하자)
	// - 아니라면 0이다.

	// 실습
	// 예시 ) 이상상태 비트플래그가 아닌 bool값으로 사용할 수도 있다. 그러나 만약 상태가 여러가지 30몇개면 엄청 많아질 것이다.
	// 0b0000 [무적][변이][스턴][공중부양]
	// 첫번째 비트 0001 공중부양, 0010 스턴, 0100 변이, 1000 무적, 1100 무적+변이
	// 각 비트에 의미를 부여할 수 있다.
	// bool 노가다를 막을 수 있다.

	/*무적상태*/
	flag = (1 << 3);

	/* 변이 상태를 추가 (무적 + 변이) */
	// flag |= 4; // 이것도 가능하겠지만.
	flag |= (1 << 2);

	// 무적인지 확인하고 싶다? (다른 상태는 관심 없음)
	// bitmask : 필요 없는 부분은 가린다.
	bool bInvisible = ((flag & (1 << 3)) != 0);

	// 무적이거나 스턴 상태인지 확인하고 싶다면?
	bool bStunOrInvicible = ((flag & 0b1010) != 0);

#pragma endregion

}

 

잘 사용하진 않으나 종종 사용한다. 알아두는 편이 좋다. 이런 것이 있다 정도

  • 종종 비트 단위의 조작이 필요할 때 사용된다.
  • 게임 서버 ID를 만들거나, 꾸겨서 넣다 보니 비트 조작이 필요할 때가 있다.
  • 대표적으로 - BitFlag

비트 연산자 : 어떤 숫자에 대해 비트 단위로 세밀하게 조작할 때 사용되며, 특정 비트를 끄거나 켜거나 하는 기능이다.

Bitwise not

  • 단일 숫자의 모든 비트를 대상으로 0은 1, 1은 0으로 ( 즉, 비트를 뒤집어 준다)

Bitwise and

  • 두 숫자의 모든 비트 쌍을 대상으로 (즉, 두 비트 사이에 값으로 판단한다)
  • 1 0 = 0, 1 1 = 1, 과 같은 식으로 작동 된다고 보면된다.

Bitwise or

  • 두 숫자 사이에 or문
  • 0 0 = 0, 1 0 = 1, 1 1 = 1 형식으로 작동된다.

 

^ bitwise XOR

  • 두 숫자의 모든 비트 쌍을 대상으로 xor한다
  • 0 0 = 0 , 1 0 = 1, 1 1 = 0, 같으면 싫다 (난 다른게 좋아 같은 느낌)

아래 코드 같이 숫자를 두번 xor하면 원복 된다. (암호화 할 때 유용한 느낌이다)

int a = 1; 
int b = 123;
a = a ^ b;
a = a ^ b;
// 다시 원상 복귀 된다. 1이였던 숫자가 다시 튀어나온다는 것 

<< bitshift 좌측

만약 0110을 맨앞에 작업을 해주고 싶을 때 코드상에 6이라는 숫자을 만들고 시프트 연산을 이용해서 옮겨줘서 만들어주는 것 해당 만들어주고자 하는 숫자가 너무 크기 때문에 작업을 어떻게 활용할 수 있을 것 같다.

 

 

 

>> bitshift 우측

예를 들어 unsigned가 아닌 경우 부호 비트가 있는 숫자이고,하필 음수였다는 값이라면

음수 부호가 붙은 unsigned 형태가 아닌 값은 우측 시프트 연산을 해도 비트에 1은 그대로 이다.

즉 음수 부호가 있는 값은 그대로 유지된 채 적용된다.

 

Unsigned 할 때에 그냥 앞에 부호가 없는 0 비트라면 그냥 무조건 0 으로 채워진다.

비트 단위 작업을 할 때 반드시 그것을 unsigned 형태로 만들어야 정신건강에 좋다.

 

 

실습

바로 넣어서 사용할 수도 있지만.

우리가 만약 엄청나게 많은 상태를 처리해야 할 때 문제가 생기는게 있는데 바로 넣게 되면 저렇게 위와 같이 저렇게 복잡한 숫자를 다 기억하면서 까지 처리해줄 수는 없다.

 

 

그래서 바로 넣기보다 조립해서 넣는 것이 좋다.

 

무적상태

unsigned char flag;
flag = 8; ---- 1
...
flag = (1 << 3); ---- 2

  • 2번째 방법 처럼 하면 8 값으로 컴파일러가 대체해준다.
  • 두번째 방법이 의미가 좀더 잘 전달될 수 있다.

예시로 무적이라는 것을 만약 3번으로 셋팅한다 했을 때 const int INVISIBLE = 3; 이런식으로 상수로 쓸 수 있을 것이다.

무적인지 아닌지 확인할 때

flag & (1 << 3) : 무적에 대한 정보만 추출 (1 << 3) , flag에 해당 마스크를 씌어준다.

최종 결과물이 0인지 아닌지 확인하면 무적인지 아닌지 확인 해 볼 수 있다.

bool bInvisible = ((flag & (1 << 3)) != 0);

 

 

예시로

 

위처럼 수식이 입력되었을 때 나머지 & 연산 비트마스크 연산에 의해 나머지는 0이 될 것이다.

: 가면을 씌어서 원하는 비트를 추출했다 라는 의미로 보면 된다.

 

 

무적이거나 스턴 상태인지 확인하고 싶다면

bool bStunOrInvicible = ((flag & 0b1010) != 0);

이런식으로 해서 현재 들어온 flag의 값과 1010이 무적과 스턴 상태 이니까. 여기서 0이 아닌 값을 추출할 수 있다.

위 처럼 하드코딩이 아닌 아래와 같이 코드를 작성할 수 도 있다.

 

bool bMask = (1 << 3) | (1 << 1); 
bool bStunOrInvicible = ((flag & bMask) != 0);

 

실행

조사식에서 확인해볼 수 있다.

 

디버깅 모드에서 처음 확인할 부분에 중단점 해두고 flag를 조사식으로 확인해보면 0으로 되어있는데 (1 << 3)이 되면 8번으로 값이 셋팅된 것을 볼 수 있다.

 

	flag = (1 << 3);
01001856  mov         byte ptr [flag (0100A1B8h)],8  
  • 강제로 그냥 대입하는 것을 볼 수 있다. flag에 그래서 8로하든 비트연산자를 쓰든 차이가 없다는 것이다.

 

 

 

다음으로 flag |= (1 << 2); 여기에서 다음으로 2번 이동한 곳에 값을 1로 바꿔줘서 변이상태를 추가해준다.

	/* 변이 상태를 추가 (무적 + 변이) */
	// flag |= 4; // 이것도 가능하겠지만.
flag |= (1 << 2);
0100185D  movzx       eax,byte ptr [flag (0100A1B8h)]  
01001864  or          eax,4  
01001867  mov         byte ptr [flag (0100A1B8h)],al  
  • Assembly에서도 or 처럼 작성해서 작동된다.

 

 

무적인지 확인하는 부분은 bool로 해당 부분이 0인지 아닌지 확인 한다. 1이니까. true가 추출 될 것이다.

	// 무적인지 확인하고 싶다? (다른 상태는 관심 없음)
	// bitmask : 필요 없는 부분은 가린다.
	
bool bInvisible = ((flag & (1 << 3)) != 0);
0100186C  movzx       eax,byte ptr [flag (0100A1B8h)]  
01001873  and         eax,8  
01001876  je          __$EncStackInitStart+48h (01001884h)  
01001878  mov         dword ptr [ebp-0DCh],1  
01001882  jmp         __$EncStackInitStart+52h (0100188Eh)  
01001884  mov         dword ptr [ebp-0DCh],0  
0100188E  mov         cl,byte ptr [ebp-0DCh]  
01001894  mov         byte ptr [bInvisible],cl 
  • 형광색으로 다른지 체크 되는 부분이 저렇게 동작하는 것을 볼 수 있다.

 

 

무적인지 스턴인지 확인할 때도 동일하다.

	// 무적이거나 스턴 상태인지 확인하고 싶다면?
bool bStunOrInvicible = ((flag & 0b1010) != 0);
01001897  movzx       eax,byte ptr [flag (0100A1B8h)]  
0100189E  and         eax,0Ah  
010018A1  je          __$EncStackInitStart+73h (010018AFh)  
010018A3  mov         dword ptr [ebp-0DCh],1  
010018AD  jmp         __$EncStackInitStart+7Dh (010018B9h)  
010018AF  mov         dword ptr [ebp-0DCh],0  
010018B9  mov         cl,byte ptr [ebp-0DCh]  
010018BF  mov         byte ptr [bStunOrInvicible],cl  
  • 숫자만 0A로 바꿔진 것을 볼 수 있다. HEX 값 A 가 들어간 것을 알 수 있다.\

 

 

비트 플래그가 어려울 수도 있으나 이해만 한다면 간단한 것이다. 

728x90
728x90
LIST
profile

평생 공부하는 빠타박스 블로그 : Learning is Happiness

@공부하는 PPATABOX

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!