이 포스트는 NAVER D2에 게시된 한글 인코딩의 이해 1편: 한글 인코딩의 역사와 유니코드을 읽고 정리한 것이다.
제대로 보이는 한글 파일을 내려받았는데 파일 이름이 깨져 보이는 현상은 왜 그런 것일까? 컴퓨터가 한글을 다루는 여러 가지 방법이 있는데 그 방식이 서로 일치하지 않기 때문이다.
지금은 컴퓨터로 한글을 사용하는 것이 당연하게 느껴지지만 컴퓨터가 한글을 제대로 표현하기 위한 한글 인코딩 체계가 수립되기까지 수십 년의 세월이 걸렸다.
탄생 초기 컴퓨터는 수치 계산만 다뤘지만 점차 기능이 확장되면서 문자를 표현해야 할 필요가 생겼다. 다른 기종의 컴퓨터가 문자를 교환하기 위해서는 표준이 필요했고, ASCII(American Standard Code for Information Interchange)와 같은 표준 문자 인코딩이 만들어졌다.
문자를 표현하기 위해서는 가장 먼저 '문자 집합'을 정의해야 한다. 영어 알파벳의 경우 'A' ~ 'Z'(대문자), 한글의 경우 '가' ~ '힣'까지가 된다. 물론 문자 뿐 아니라 개행(line feed, LF), 공백(space) 등 제어 문자도 포함된다. 이러한 문자 집합을 코드 형태(일반적으로 행렬)로 표현한 것을 코드화된 문자 집합(CCS, coded character set)이라고 한다.** 예를 들어 '가'에는 10001, '각'에는 10002와 같이 코드를 할당하는 방식이다.
그리고 문자 집합을 컴퓨터에 저장하기 위해서 옥텟(octet, 8비트 또는 1바이트 단위) 형태로 표현한 것을 인코딩 방식(CES, character encoding scheme)이라고 한다.
문자 집합의 시초인 ASCII는 0x00부터 0x7F까지의 총 127개 문자(제어 문자, 특수 문자, 숫자, 알파벳 등)로 이루어져 있다. 그런데 ASCII는 미국에서 제정된 표준이라 유럽어의 'Ü'와 같은 문자는 표현할 수 없었다.
이를 해결하기 위해 확장 ASCII(Extended ASCII)를 제정하여 기존의 ASCII로 정의하지 못했던 128번부터 255번까지의 새로운 문자를 정의할 수 있게 되었다.
유럽어는 알파벳을 기초로 사용하므로 256가지의 확장 ASCII로 표현이 가능하다. 그러나 중국, 일본, 한국(CJK, Chinese-Japanese-Korean)에서 사용하는 문자 집합은 그보다 훨씬 크기 때문에 다른 방안이 필요하다.
한글 표현 방법은 크게 조합형과 완성형으로 나눌 수 있다. 완성형이란 '가', '각', '간'과 같은 완성된 문자에 코드를 할당하는 방식이고, 조합형은 한글의 제자 원리에 기반하여 초성, 중성, 종성에 각각 코드를 할당하는 방식이다.
좀 더 세분화하면 N바이트 조합형, 3바이트 조합형, 7비트 완성형, 2바이트 조합형, 2바이트 완성형으로 나눌 수 있다. 이 중 완성형이 한글 표준안으로 채택되었고, 따라서 유니코드의 한글 표현 방식에도 완성형이 먼저 채택되었다.
본격적으로 한글 문자 집합과 한글 글꼴이 운영체제에 포함되기 전인 DOS(Disk Operating System) 시절(Microsoft Windows 95 이전)에는, 결과를 출력하기 위해 BIOS(Basic Input/Output System)를 직접 제어하거나 '한글 카드'라 불리는 ISA(Industry Standard Architecture) 인터페이스와 같은 별도의 하드웨어를 사용해야 했다.
한국어 뿐 아니라 중국어와 일본어에도 독자적인 문자 집합이 있다. 유럽어끼리도 표준에 따라 글자에 매핑된 코드가 다르기 때문에 하나의 문자 집합만 사용하는 경우 여러 언어를 동시에 표현할 수 없다.
이 문제를 해결하기 위해 전 세계적으로 사용되는 모든 문자 집합을 하나로 모아 탄생시킨 것이 유니코드이다. ISO 10646 표준에서 UCS(Universal Character Set)을 정의하고 있다. 유니코드 1.0.0은 1991년 8월 제정되었으며, 그 후 약 5년이 지나서야 유니코드 2.0.0에 한글 11,172자가 모두 포함되었다.
유니코드 값을 나타내기 위해서는 코드 포인트(code point)를 사용하는데, 보통 U+를 붙여 표시한다. 예를 들어, 'A'의 유니코드 값은 U+0041로 표현한다(\u0041로 표기하기도 함). 유니코드는 공식적으로 31비트 문자 집합이지만 현재까지는 21비트 이내로 모두 표현이 가능하다.
유니코드는 논리적으로 평면(plane)이라는 개념을 이용하여 구획을 나누며, 평면 개수는 0번 평면인 기본 다국어 평면(BMP; Basic Multilingual Plane)에서 16번 평면까지 모두 17개이다. 대부분의 문자는 U+0000~U+FFFF 범위에 있는 기본 다국어 평면에 속하며, 일부 한자는 보조 다국어 평면(SMP, Supplementary Multilingual Plane)인 U+10000~U+1FFFF 범위에 속한다.
한글은 U+1100~U+11FF 사이에 한글 자모 영역, U+AC00~U+D7AF 사이의 한글 소리 마디 영역에 포함된다.
한글 자모 영역은 각 자음과 모음마다 코드를 부여하고, 한글 소리 마디 영역은 자모음이 조합된 음절(완성형)마다 코드를 부여했다. 덕분에 한글을 조합형 또는 완성형으로 표기할 수 있어서 옛 한글 표기가 가능하며 만일에 새로운 한글 음절이 생기더라도 유니코드 문자 집합에 추가하기가 용이하다.
유니코드의 한글 표현은 2편에서 자세히 설명한다(정리한 포스트).
유니코드의 인코딩 방식으로는 코드 포인트를 코드화한 UCS-2와 UCS-4, 변환 인코딩 형식(UTF, UCS Transformation Format)인 UTF-7, UTF-8, UTF-16, UTF-32 인코딩 등이 있다. 이 중 ASCII와 호환이 가능하면서 유니코드를 표현할 수 있는 UTF-8 인코딩이 가장 많이 사용된다.
코드 포인트 범위 | 비트 수 | 인코딩 |
---|---|---|
U+0000~U+007F | 7 | 그대로 인코딩 |
U+0080~U+07FF | 11 | 110xxxxx 10xxxxxx |
U+0800~U+FFFF | 16 | 1110xxxx 10xxxxxx 10xxxxxx |
U+10000~U+1FFFFF | 21 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
위의 표에서 xxxx
로 표시된 부분에는 원래의 비트 값을 순서대로 적는다.
'한'을 UTF-8로 인코딩하는 과정을 예를 들어보겠다 :
한
'의 코드 포인트는 U+D55C
이다.D55C
를 2진수로 변환하면 110101010110011100
이다.1110xxxx 10xxxxxx 10xxxxxx
에서 x
가 있는 자리에 차례대로 삽입하면 11101101 10010101 10011100
이 된다. 0xED
0x95
0x9C
이 된다.위에서 보듯 UTF-8에서 현대 한글에서 사용하는 글자들(U+AC00 ~ U+D7AF)은 모두 3 바이트로 인코딩된다.
HTTP 폼 요청(application/x-www-form-urlencoded
) 등에서 사용되는 URL 인코딩은 US-ASCII를 사용한다.
한글처럼 US-ASCII 문자 집합에 없는 문자는 UTF-8 규칙을 따라 인코딩한 뒤 %
기호를 붙여 %XX
형태로 변환한다. 따라서 URL 인코딩을 퍼센트 인코딩이라고도 한다.
UTF-8에서 한글은 초성, 중성, 종성 1바이트씩 3바이트로 변환된다. 예를 들어 '한'의 경우 '%ED%95%9C
'로 인코딩된다.