연산자 두 번째 포스팅입니다. 지난 포스팅에서는 산술 연산자에 대해 알아봤습니다. 이번에는 단항 연산자에 대해 알아보겠습니다.

1.2 단항 연산자

수학에서는 단항 연산자가 둘 있습니다. 가감승제 중에서 더하기와 뺄셈만 제공합니다. 아시다시피 숫자 앞에 + 기호를 붙인다고 숫자의 부호가 바뀌지는 않습니다. 하지만 - 기호를 붙이면 부호가 바뀝니다. 초등학교 때 배운 개념이니 그냥 사용법만 알고 넘어가면 되겠네요.

C 언어에서는 총 4가지 단항 연산자를 제공합니다.

연산자 설명
+ 피연산자의 부호에 + 연산을 합니다. 기존 값을 바꾸지는 않습니다.
피연산자의 부호를 -는 +, +는 -로 바꿉니다.
~ 비트 보수(또는 비트 NOT) 연산자입니다. 피 연산자를 2의 보수로 만듭니다.
! 논리 부정(논리 NOT) 연산자입니다. 피 연산자가 true(0이 아닌 값)이면 값 0을 생성하고, false(0값)이면 값 1을 생성합니다.

~는 틸드라고 읽습니다. !는 영어로는 exclamation point인데 익스클라메이션 포인트 또는 엑스클라메이션 포인트라고 읽습니다. 물론 느낌표라고 읽어도 됩니다.

각 연사자가 어떻게 동작하는지 코드로 살펴보겠습니다.

#include <stdio.h>

int main(void) {
	int a = -5;
	int b = -3;
	int c = 0x11111110;
	bool d = true;
	
	printf("a = %d, +a = %d\n", a, +a);
	printf("b = %d, -b = %d\n", b, -b);
	printf("c = 0x%x, ~c = 0x%x\n", c, ~c);
	printf("d = %d, !b = %d\n", d, !d);

	return 0;
}

[결과]

a = -5, +a = -5
b = -3, -b = 3
c = 0x11111110, ~c = 0xeeeeeeef
d = 1, !b = 0

-, +, !는 예상한 결과가 나왔을 겁니다. ~를 살펴보면 처음보는 0x라는 기호가 보이네요. 이렇게 0x를 값 앞에 붙이면 16진수로 인식합니다. 16진수는 0부터 15까지 표현할 수 있는 표현법입니다.

0b를 붙이면 2진수로 인식합니다. 아무 기호도 붙지 않으면 10진수로 인식합니다.

0~15까지 10진수, 2진수, 16진수 표현은 다음과 같습니다.

[표] 0~15까지 10진수, 2진수, 16진수 표현

10진수 2진수 16진수
0 0b0000 0x0
1 0b0001 0x1
2 0b0010 0x2
3 0b0011 0x3
4 0b0100 0x4
5 0b0101 0x5
6 0b0110 0x6
7 0b0111 0x7
8 0b1000 0x8
9 0b1001 0x9
10 0b1010 0xa
11 0b1011 0xb
12 0b1100 0xc
13 0b1101 0xd
14 0b1110 0xe
15 0b1111 0xf

16진수에서 10부터 15까지는 a~f로 표현되는군요. 그렇다면 ~c의 출력에 있는 e는 14, f는 15를 뜻하는 거군요!
지금까지 간단하게 관우와 단항 연산자에 대해 알아보았습니다. 그런데 제 친구 제갈량이 뭔가 더 보탤 말이 있나보군요!

제갈량의 동남풍 : 2의 보수가 뭐예요?

안녕하세요? 제갈량입니다.

앞에서 다룬 예제에서 c의 의미를 알았으니 c와 ~c를 더해보겠습니다.

0x 1111 1110
0x eeee eeef
===========
0x ffff ffff

두 수를 합쳤더니 0xffffffff이 되었네요. 이를 2진수로 표현하면 다음과 같습니다.

0b 1111 1111 1111 1111 1111 1111 1111 1111

모든비트가 1이 되었군요. ~는 2의 보수를 출력하는 연산자라고 했습니다.

원본을 더했을 때 모든 비트가 1이 되는 값을 2의 보수라고 합니다.

제갈량의 동남풍 : 보수 어디에 써요?

2의 보수가 무엇인지는 알겠는데 그럼 어디에 쓸까요?

컴퓨터는 모든 연산이 2진수 기반으로 이뤄집니다. 0 아니면 1만 있는 거죠.

예를 들어 8비트 연산을 수행하는 경우 정수 1을 다음과 같이 표현합니다.

0b 0000 0001

그렇다면 -1은 어떻게 표현할까요?

-1은 1의 보수에 1을 더하면 됩니다.

 0b 1111 1110
+ 0b 0000 0001
==============
0b 1111 1111

물론 자료형이 singed여야 합니다. 어차피 unsigned일 때는 음수 자체가 성립되지 않죠. 자료형이 unsigned이면 맨 최상위 비트(most significant bit, MSB)는 부호를 결정합니다. 0이면 양수, 1이면 음수입니다.

최상위 비트, MSB라는 말을 처음 언급하네요.

MSB는 최곳값을 갖는 비트 위치입니다.

최곳값, 즉 제일 큰 값을 표현하는 비트의 위치라고 알아두시면 됩니다.

참고로 MSB의 반댓말은 LSB입니다.

최하위 비트(least significant bit, LSB)는 가장 작은 값을 갖는 비트 위치입니다.

제갈량의 동남풍 : MSB는 가장 왼쪽 비트인가요?

오늘은 제갈량이 아주 바쁘네요.

어려운 내용이지만 임베디드뿐만 아니라 네트워크 프로그래밍할 때도 간혹 발생할 수 있는 문제가 있어 추가 설명을 해드립니다.

사람이 논리적으로 계산할 때는 가장 왼쪽 비트가 MSB라고 생각하지만, 컴퓨터에서 저장되거나 연산될 때 경우에 따라서는 그렇지 않을 수도 있습니다.

컴퓨터에서 1바이트(8비트) 단위로 저장하는 순서를 엔디안이라고 합니다.

MSB가 가장 왼쪽 비트일 때는 빅엔디안 방법입니다.

MSB가 가장 오른쪽 비트일 때는 리틀엔디안 방법입니다.

사람 입장에서 12345678이라는 숫자에서 MSB는 당연히 1부분일 겁니다. 따라서 직관적으로 다음과 같이 메모리에 저장하려들겠죠.

주소 (바이트 단위) 100번지(MSB) 101번지 102번지 103번지
12 34 56 78

그런데 이렇게 저장을 하면 컴퓨터 입장에서 12345678에 1을 더할 때 4바이트를 모두 읽어야 합니다.

리틀엔디안 방식으로 저장하면 처음 1바이트만 읽어와 연산하면 그만이죠. 술연산유닛(ALU)는 메모리를 낮은 주소부터 읽기 때문에 가능한 거죠.

주소 (바이트 단위) 100번지 101번지 102번지 103번지(MSB)
78 65 43 12

그렇지만 리틀엔디안은 사람 입장에서는 해석하는 데 어려움이 있습니다. 특히나 메모리에 어떤 값이 있는지 2진수나 16진수 값으로 디버깅할 때 많이 곤란합니다.

PC에서 사용하는 인텔 CPU는 리틀엔디안 방식을 사용합니다. 그런데 RISC 계열 CPU와 네트워크에서는 빅엔디안 방식을 사용합니다.

이상 제갈량이었습니다!