컴퓨터가 이해하는 정보
⭐ 이 포스팅은 위 책의 Chapter02 컴퓨터 구조 - 2 컴퓨터가 이해하는 정보 (45p ~ 65p) 분량의 내용을 정리했습니다.
CPU는 기본적으로 0과 1만을 이해할 수 있습니다. 여기서 0과 1을 나타내는 가장 작은 정보의 단위를 비트(bit)라고 합니다. N비트는 2^N개의 정보를 표현할 수 있습니다.
구분 | 비트 |
1 byte | 8 비트 |
1 kB | 1,000 바이트 |
1 MB | 1,000 킬로바이트 |
1 GB | 1,000 메가바이트 |
1 TB | 1,000 기가바이트 |
비트, 바이트, 킬로바이트, 메가바이트, 기가바이트, 테라바이트는 모두 프로그램의 크기를 나타낼 때 사용하는 정보 단위입니다. CPU 관점에서의 정보 단위로는 워드가 있습니다. 워드(word)란 CPU가 한 번에 처리할 수 있는 데이터의 크기를 의미합니다. 워드의 크기는 CPU마다 다르지만, 현대 컴퓨터 대부분의 워드 크기는 32비트, 혹은 64비트입니다.
데이터 - 0과 1로 숫자 표현하기
0과 1만을 이해할 수 있는 CPU는 컴퓨터 내부에서 2진법(binary)을 사용해 2 이상, 0 이하의 수를 이해합니다. 2진수로 표현된 수는 숫자 뒤에 아래첨자로 (2)를 붙이거나 2진수 앞에 0B를 붙입니다.
2진수로 소수를 나타내는 방법
오늘날 대부분의 컴퓨터는 2진수의 지수와 가수를 다음과 같은 형식으로 저장합니다. 이와 같은 부동 소수점(floating point) 저장 방식을 IEEE 754라고 합니다.
부호(sign) 비트가 0이면 양수를 의미하고, 1이면 음수를 의미합니다.
그림과 같은 형태로 소수가 저장된다고 할 때, 가수의 정수부에는 1로 통일된 정규화한 수(normalized number)가 저장됩니다. 컴퓨터가 지수를 저장할 때는 바이어스(bias) 값이 더해져서 저장되며, 이때 바이어스 값은 $2^{k-1}-1$(k는 지수의 비트수)입니다. 지수를 표현하기 위해 8비트가 사용되었다면 바이어스 값은 127이고, 11비트가 사용되었다면 바이어스 값은 1,023 입니다.
여기서 유의할 점은 10진수 소수를 2진수로 표현할 때, 10진수 소수와 2진수 소수의 표현이 딱 맞아떨어지지 않을 수 있다는 점입니다. 컴퓨터의 저장공간이 한정적이기 때문에 무한히 많은 소수점을 저장할 수는 없죠. 그래서 딱 맞아떨어지지 않는 소수를 표현할 때는 일부 소수점을 생략하여 저장합니다. 그래서 오차가 발생하는 것입니다.
데이터 - 0과 1로 문자 표현하기
컴퓨터가 이해할 수 있는 문자들의 집합은 문자 집합(character set)이라고 합니다. 그리고 문자 집합에 속한 문자를 컴퓨터가 이해하는 0과 1로 이루어진 문자 코드로 변환하는 과정을 문자 인코딩(character encoding)이라고 합니다. 동일한 문자 집합이라 하더라도 다양한 문자 인코딩 방법이 있을 수 있습니다. 반대로, 0과 1로 표현된 문자를 사람이 이해하는 문자로 변환하는 과정은 문자 디코딩(character decoding)이라고 합니다.
아스키(ASCII, American Standard Code for Information Interchange)
아스키는 초창기 컴퓨터에서 사용하던 문자 집합 중 하나로, 영어의 알파벳과 아라비아 숫자, 일부 특수 문자를 포함합니다. 하나의 아스키 문자를 표현하기 위해서는 8비트(1바이트)를 사용합니다. 8비트 중 1비트는 패리티 비트(parity bit)라고 불리는데, 이는 오류 검출을 위해 사용되는 비트이기 때문에 실질적으로 문자 표현을 위해 사용되는 비트는 7비트입니다. 7비트로 표현할 수 있는 정보의 가짓수는 $2^7$개이므로 총 128개의 문자를 표현할 수 있습니다. 아스키 문자에 대응된 고유한 수를 아스키 코드라고 합니다.
EUC-KR
EUC-KR은 KS X 1001, KS X 1003이라는 문자 집합 기반의 인코딩 방식으로, 아스키 문자를 표현할 때는 1바이트, 하나의 한글 글자를 표현할 때는 2바이트 크기의 코드를 부여합니다. 2바이트(16비트)는 네 자리 16진수로 표현할 수 있으므로 EUC-KR로 인코딩된 한글 글자 하나는 네 자리 16진수로 나타낼 수 있습니다.
유니코드(Unicode)
유니코드는 한글을 포함해 EUC-KR에 비해 훨씬 많은 언어, 특수문자, 화살표, 이모티콘까지 코드로 표현할 수 있는 통일된 문자 집합입니다. 유니코드가 없었다면 각각의 언어마다 다른 문자 집합과 인코딩 방식을 이해해야 했겠지만, 유니코드가 대부분의 언어를 지원하기 때문에 국가별로 다른 문자 집합과 인코딩 방식을 준비할 필요가 없어졌습니다. 이런 점에서 유니코드는 현대 가장 많이 사용되는 표준 문자 집합이며, 문자 인코딩에 있어 매우 중요한 역할을 맡고 있습니다.
유니코드가 문자의 집합이라면 UTF-8, UTF-16, UTF-32는 유니코드 문자에 부여된 값을 인코딩하는 방식을 말합니다. UTF-8, UTF-16, UTF-32는 가변 길이 인코딩 방식입니다. 인코딩된 결과의 길이가 일정하지 않을 수 있다는 의미입니다.
base64
base64는 비단 문자뿐만 아니라, 이진 데이터까지 변환할 수 있는 인코딩 방식입니다. 사실 문자보다는 이진 데이터를 인코딩하는 데에 더 많이 사용됩니다. base64 인코딩은 이미지 등 단순 문자 이외의 데이터까지 모두 아스키 문자 형태로 표현할 수 있습니다.
base64는 사실 64진법을 의미합니다. 그래서 하나의 base64 인코딩 값을 표현하기 위해 64개의 문자가 사용되는 것입니다. 64진수 하나를 표현하기 위해서는 $2^6$의 지수인 6비트가 필요합니다. ‘ab’의 경우 총 16비트로 표현되기 때문에 6비트로 나누어 떨어지지 않는 자리는 0으로 채워지는 패딩(padding)이 되고, 이는 ‘=’로 인코딩됩니다.
명령어
명령어는 수행할 동작과 수행할 대상으로 이루어져 있습니다. 수행할 대상은 다음과 같이 동작에 사용될 데이터 자체가 될 수도 있고, 동작에 사용될 데이터가 저장된 위치가 될 수도 있습니다.
수행할 동작 | 수행할 대상 | 수행할 대상 |
더해라 | 100과 | 120을 |
빼라 | 메모리 32번지 안의 값과 | 메모리 33번지 안의 값을 |
저장해라 | 10을 | 메모리 128번지에 |
이때 ‘명령어가 수행할 동작’은 연산 코드(opcode)라고 하고, ‘동작에 사용될 데이터’ 혹은 ‘(메모리나 레지스터의 주소와 같이)동작에 사용될 데이터가 저장된 위치’는 오퍼랜드(operand)라고 합니다. 즉, 하나의 명령어는 연산 코드와 0개 이상의 오퍼랜드로 구성되어 있으며, 명령어에서는 연산 코드가 담기는 영역은 연산 코드 필드, 오퍼랜드가 담기는 영역은 오퍼랜드 필드라고 합니다.
오퍼랜드 필드에는 숫자나 문자와 같이 연산 코드에 사용될 데이터가 직접 명시되기보다는 많은 경우 연산 코드에 사용될 데이터가 저장된 위치, 즉 메모리 주소나 레지스터의 이름이 명시됩니다. 그래서 오퍼랜드 필드를 주소 필드(address field)라고 부르기도 합니다.
대부분의 CPU가 공통적으로 이해하는 대표적인 연산 코드의 유형에는 데이터 전송, 산술/논리 연산, 제어 흐름 변경, 입출력 제어가 있습니다.
NOTE 연산 코드는 연산자, 오퍼랜드는 피연산자라고도 부릅니다.
기계어와 어셈블리어
CPU가 이해할 수 있도록 0과 1로 표현된 정보를 잇는 그대로 표현한 언어를 기계어(machine code)라고 합니다. 하지만 기계어만 봐서는 이것이 어떤 프로그램인지, 어떻게 동작하는지 쉽사리 짐작하기가 어렵습니다. 그래서 등장한 언어가 어셈블리어(assembly language)입니다. 어셈블리어는 0과 1로 표현된 기계어를 읽기 편한 형태로 단순 번역한 언어입니다.
하지만 같은 프로그램일지라도 CPU마다 이해하는 명령어가 다르면 실행이 불가할 수 있다는 점은 기억해 두어야 합니다. 일례로, 인텔 CPU를 사용하는 컴퓨터에서 만들어진 실행 파일을 애플 CPU를 사용하는 컴퓨터에 그대로 옮겨서 실행할 수 없듯, 여러 플랫폼에서 실행되는 프로그램을 개발할 때는 특정 CPU에만 의존적인 코드로 만들지 않아야 합니다.
명령어 사이클
CPU가 명령어를 처리하는 과정에서 프로그램 속 각각의 명령어들은 일정한 주기를 반복하며 실행되는데, 이 주기를 명령어 사이클(instruction cycle)이라고 합니다. 명령어 사이클의 첫 번째 과정으로, 메모리에 있는 명령어를 CPU로 가지고 오는 단계를 인출 사이클(fetch cycle)이라고 합니다. 명령어 사이클의 두 번째 과정인 CPU로 가져온 명령어를 실행하는 단계는 실행 사이클(execution cycle)이라고 합니다. CPU는 메모리 속 명령어를 가져와 실행하고, 가져와 실행하기를 반복하며 프로그램을 실행하기 때문에 인출 사이클과 실행 사이클은 반복되게 됩니다.
하지만 명령어를 인출했더라도 곧바로 실행할 수 없는 경우도 있습니다. 오퍼랜드 필드에 메모리 주소가 명시된 경우 CPU가 명령어를 인출했더라도 한 번 더 메모리에 접근해야 합니다. 이렇게 명령어를 실행하기 위해 한 번 더 메모리에 접근하는 단계를 간접 사이클(indirect cycle)이라고 합니다. 마지막으로 인터럽트를 처리하는 인터럽트 사이클입니다.