N Log

문자 인코딩

유니코드 이전의 문자 인코딩

ASCII

ASCII(American Standard Code for Information Interchange)는 7비트로 영문 알파벳, 숫자, 특수 문자와 제어 문자를 표현하는 문자 인코딩 표준이다.
7비트는 272^7가지, 즉 128가지 값을 표현할 수 있으며, ASCII는 0부터 127까지의 각 값에 문자 또는 제어 문자를 할당한다.
ASCII 테이블은 이 대응 관계를 표로 나타낸 것이다.

RFC 20: ASCII format for Network Interchange (PDF)

ASCII는 7비트 코드다.
당시 통신 환경에서는 이 7비트를 그대로 전송하기도 했고, 전송 오류를 검출하기 위해 패리티(parity) 비트 1개를 덧붙여 8비트 단위로 전송하기도 했다.

Extended ASCII

영문 알파벳 외의 문자도 표현할 필요가 생기면서, 7비트 ASCII에 1비트를 추가한 8비트 문자 인코딩들이 등장했다.
8비트는 282^8가지, 즉 256가지의 값(0부터 255)을 표현할 수 있다.
표준화 기구와 여러 기업은 ASCII를 바탕으로 서로 다른 8비트 문자 인코딩을 만들었고, 이들을 통칭해 확장 ASCII(Extended ASCII)라고 부른다.
확장 ASCII는 하나의 문자 인코딩을 가리키는 말이 아니며, 공식 표준에서 정한 분류명도 아니다.
이들은 0부터 127까지는 ASCII와 같게 유지하지만, 128부터 255까지의 영역은 인코딩마다 다른 문자나 기호를 배치하므로 서로 호환되지 않는다.

għażżiel은 몰타어로 가젤(gazelle)을 뜻한다.
이 단어를 ISO-8859-3으로 해석하면 정상적으로 표시되지만, ISO-8859-1으로 해석하면 g±a¿¿iel처럼 표시되어 알아보기 어렵다.
이처럼 인코딩을 잘못 해석해 문자가 의도와 다르게 표시되는 현상을 주로 문자 깨짐이라고 표현한다.

Windows에서 말하는 ANSI

ANSI(American National Standards Institute)는 미국 국가표준협회이며, 문자 인코딩의 이름이 아니다.
그런데 Windows에서는 ANSI 문자 집합(character set), ANSI 인코딩(encoding), ANSI 코드 페이지(code page) 같은 용어를 마주친다.
ANSI가 문자 인코딩이 아닌데도 이러한 표현이 사용되는 이유를 알아보자.

1980년대 ANSI와 ECMA에서는 유럽 언어의 문자를 표현하기 위한 8비트 문자 집합 표준화 작업을 진행했다.
이 과정에서 만들어진 초안은 국제 표준화 과정을 거쳐 1987년 ISO 8859-1로 확정되었다.

Microsoft도 이 ANSI 초안을 바탕으로 문자 집합을 구현했고, 이것이 이후 Windows-1252로 발전했다.
Microsoft는 ISO 8859-1이 제어 문자에 할당한 일부 위치에 다른 문자를 추가했다.
따라서 Windows-1252와 ISO 8859-1은 같은 계통에서 출발해 많은 문자가 일치하지만, 서로 다른 코드 페이지다.

이러한 역사적 배경 때문에 Windows 문맥에서 ANSI라는 용어가 등장하게 되었다.

Windows는 여러 언어권을 지원하기 위해 언어권마다 서로 다른 코드 페이지를 사용했다.
서유럽에서는 Windows-1252, 키릴 문자권에서는 Windows-1251, 한국어에서는 CP949, 일본어에서는 CP932 등이 사용되었다.
각 코드 페이지는 바이트 값과 문자 사이의 대응 관계가 서로 다르다.
따라서 같은 바이트열이라도 어떤 코드 페이지로 해석하느냐에 따라 서로 다른 문자로 표시될 수 있다.

Windows에서 ANSI라는 용어는 Windows 코드 페이지를 가리키는 의미로 관행적으로 사용되지만, 이는 역사적 유래에서 비롯된 잘못된 명칭이다.
ANSI로 불리는 코드 페이지는 앞서 언급한 1252, 1251, 949, 932 등 여러 개가 있지만, 이들이 동시에 사용되는 것은 아니며 시스템 로캘(System Locale)에 따라 활성 시스템 코드 페이지가 달라진다.
예를 들어 서유럽 환경에서는 Windows-1252가, 한국어 환경에서는 CP949가 활성 시스템 코드 페이지로 사용될 수 있다.

Why is the default 8-bit codepage called “ANSI”?
Microsoft Glossary

유니코드

유니코드 이전에는 언어권과 시스템마다 서로 다른 문자 인코딩을 사용했다.
따라서 데이터를 올바르게 해석하려면 어떤 문자 인코딩이 사용되었는지 알아야 했다.
또한 각 문자 인코딩은 미리 정해진 문자 집합에 포함된 문자만 표현할 수 있었기 때문에, 서로 다른 언어권의 문자를 한 문서에서 함께 표현하기 어려웠다.

유니코드는 이러한 문제를 해결하기 위해 세계 여러 언어의 문자와 이모지를 비롯한 다양한 기호를 하나의 체계에서 일관되게 다루도록 만든 문자 표준이다.
유니코드는 각 문자와 기호에 코드 포인트(code point)라는 고유한 번호를 부여한다.
코드 포인트는 문자에 번호를 부여할 뿐, 그 번호를 어떤 비트 패턴으로 표현할지는 정하지 않는다.
UTF-8, UTF-16, UTF-32는 코드 포인트를 서로 다른 비트 패턴으로 표현하는 유니코드 인코딩 방식이다.

UTF-8

UTF-8은 유니코드 문자 하나를 최소 8비트, 최대 32비트로 표현하는 가변 길이 인코딩 방식이다.
8비트로 표현할 수 없는 문자는 16비트, 24비트, 32비트를 사용해 표현한다.
여기서 16비트나 32비트를 사용한다는 말은 UTF-16이나 UTF-32로 인코딩한다는 뜻이 아니라, UTF-8에서 8비트씩 2개 또는 4개를 사용한다는 뜻이다.

UTF-8은 가변 길이 인코딩이므로 문자를 표현하는 데 모든 비트를 사용할 수는 없으며, 몇 바이트를 읽어야 하는지에 대한 정보도 함께 저장한다.

코드 포인트 범위 바이트 수 바이트 1 바이트 2 바이트 3 바이트 4
U+0000–U+007F 1 0xxxxxxx - - -
U+0080–U+07FF 2 110xxxxx 10xxxxxx - -
U+0800–U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx -
U+10000–U+10FFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

각 바이트에서 01로 이루어진 부분은 고정된 부분이고, x 부분에는 코드 포인트의 비트가 들어간다.
첫 번째 바이트의 앞부분을 읽으면 해당 문자를 표현하기 위해 사용하는 바이트 수를 알 수 있다.
뒤따르는 바이트는 모두 10으로 시작하므로 문자 경계를 구분할 수 있다.

ASCII의 128개 문자는 유니코드에서도 같은 코드 포인트를 사용하므로 UTF-8에서 각각 1바이트로 표현할 수 있다.
AU+0041이므로 1바이트로 표현할 수 있으며, 0100 0001로 ASCII와 같은 값이다.

한글 를 어떻게 표현하는지 알아보자.
의 코드 포인트는 U+AC00이다.
코드 포인트 값만 보면 16비트로 를 표현할 수 있다.
하지만 표의 2바이트 형식에는 코드 포인트 값을 넣을 수 있는 x가 11개뿐이다.
따라서 는 코드 포인트 값을 담을 수 있는 x가 16개인 3바이트 형식으로 표현해야 한다.

U+AC00을 2진수로 나타내면 1010 1100 0000 0000이다.
3바이트 형식의 x 16개에 이 값을 앞에서부터 차례로 넣으면 다음과 같다.

코드 포인트의 비트 UTF-8 바이트 1 UTF-8 바이트 2 UTF-8 바이트 3
1010 1110 + 1010
110000 10 + 110000
000000 10 + 000000

따라서 는 UTF-8에서 11101010 10110000 10000000, 즉 EA B0 80으로 표현된다.

UTF-16

UTF-16은 유니코드 문자 하나를 최소 16비트로 표현하는 가변 길이 인코딩 방식이다.
대부분의 문자는 16비트로 표현하고, 16비트 범위를 벗어나는 일부 문자는 32비트로 표현한다.

CJK 문자를 대부분 16비트로 표현할 수 있어, UTF-8보다 적은 공간을 차지할 수 있다.
다만 ASCII는 8비트인데 UTF-16은 최소 16비트를 사용하므로 ASCII와 바이트 단위로 호환되지 않으며, ASCII 문자마다 16비트를 사용해야 한다.
또한 바이트를 저장할 때 어떤 순서로 저장하고, 읽을 때 어떤 순서로 해석할지 정해야 하는 빅 엔디언과 리틀 엔디언 문제가 있다.

UTF-32

UTF-32는 유니코드 문자 하나를 32비트로 표현하는 고정 길이 인코딩 방식이다.
유니코드 코드 포인트 하나를 32비트 공간에 그대로 넣을 수 있어 코드 포인트와 1:1로 대응한다.
다만 모든 문자에 32비트를 사용하므로 공간 낭비가 크고, 빅 엔디언과 리틀 엔디언 문제가 있다.