컴퓨터+과학

한글 인코딩 이야기 - (2) 유니코드, UCS-2, UTF-8, UTF-16

NOAH`s window 2010. 12. 20. 12:43

조합형, 완성형에 대한 이야기에 이어서 이번에는 유니코드에 대한 이야기를 해보자

 

(1) 유니코드

전에 이야기한 것처럼 유니코드가 등장하기 전에는 각각의 글자체계를 표현하기 위한 인코딩 방식이 각각 존재했다. 127이하의 영역은 통일되있었지만, 128이상의 영역은 통일된 방식이 없이 각자의 글자체계를 표현하기 위해서 사용됐기 때문이다. 128이상의 영역을 해석하는 방법에 대한 약속 중 하나가 코드 페이지라는 개념이었다. 하지만 인터넷의 보급과 함께 다국어 지원에 대한 필요성이 커지면서 기존 체계에 문제점이 생겼다. 동일한 프로그램의 한국어 버전, 중국어 버전, 일본어 버전을 개발한다고 하면, 각 언어로 작성된 문장들을 물론이고, 각 언어에 맞는 인코딩 방식까지 작성해야 했다. 또다른 문제는 각 언어를 동시에 표현할 방법이 없다는 점이다. 같은 바이트 배열이라도 각 인코딩 방식에 따라 해당하는 글자가 다르기 때문이다(코드 페이지를 전환하는 수밖에 없다).

이런 두가지 문제를 해결하기 위해서 세상의 모든 글자를 하나의 체계로 표현하기 위해서 등장한 것이 유니코드다. 간단히 생각하면 유니코드는 세계에 존재하는 모든 글자를 모은 후에 이 글자들에 코드를 부여한 것이다(실제로는 이렇게 단순하지는 않지만 우리의 논의를 위해서는 이정도의 개념이면 충분하다). 한글의 완성형 방식을 생각하면 쉽다. 다만 대상이 한글이 아니라 전세계의 모든 글자일 뿐이다.

한글 완성형에서는 기존의 아스키 코드는 127이하에 할당되고, 추가적인 부분은 최대 2바이트의 128이상에 할당됐다. 그렇다면 유니코드는 어떤가? 유니코드는 약간 다른 개념이 등장한다. 유니코드의 각 글자는 "코드 포인트"라는 숫자(현재까지는 21비트이지만 앞으로 계속 확장 될 것이다)에 대응된다. 예를 들어 'A'는 U+41에 대응한다. 여기에서 U+는 이 숫자가 코드 포인트라는 의미이다. 코드 포인트는 어디까지나 추상적인 개념일 뿐 실제로 코드 포인트가 메모리나 디스크에서 표현되는 방식은 따로 있다. 그리고 이런 방식이 바로 UTF-8이나 UTF-16이니 하는 인코딩 방식이다.


 

(2) UCS-2

유니코드에 대한 가장 흔한 오해 중의 하나는 "유니코드는 2바이트 체계이고 65,536글자를 표현할 수 있다"라는 점이다. 아마 이 오해는 초창기의 유니코드 방식 중 하나인 UCS-2때문에 생긴 듯 하다. UCS-2는 유니코드의 코드 포인트를 2바이트로 인코딩한다. UCS-2에서 문제가 되는 것은 바이트 순서(Byte order), 또는 Endian이다. 'A'를 00 48 으로 표현할 것이가, 48 00 으로 표현할 것인가? Big Endian을 사용하는 CPU도 있고 Little Endian을 사용하는 CPU도 있기 때문에, 효율성을 위해서 UCS-2의 Endian을 강제하기 보다는 각각의 시스템에 이를 맡기고, 대신 문자열이 어떤 Endian방식을 사용하는지 명기하도록 하는 방식을 선택했다. 이것이 Byte Order Mark, 즉 BOM이다. UCS-2 인코딩을 사용하는 문자열은 실제 문자들이 시작하기 전에, 즉 문자열의 처음에 BOM을 추가해야한다. BOM은 FE FF 또는 FF FE 라는 2바이트다. UCS-2 인코딩 방식의 문자열을 읽을 때 처음 2바이트가 FE FF인지 FE FF인지에 따라 나머지 부분을 해석하는 방식을 다르게하면 된다. FE FF라면 'A'가 00 48이고 FF FE라면 'A'가 48 00 으로 저장됐다고 생각하면 된다.

BOM의 등장만 해도 문제가 복잡한데, 더 심각한 문제는 모든 UCS-2 방식의 문자열들이 BOM을 달고 있지는 않다는 점이다. 동일한 시스템에서만 문자열을 주고 받는다면 문제가 없지만 다른 바이트 순서를 사용하는 시스템간에 문자열을 주고 받을 때는 분명히 문제가 된다.

이보다 더 심각한 UCS-2의 문제는 UCS-2가 2바이트를 사용하기 때문에 표현할 수 있는 글자의 한계가 65,536글자라는 점이다. 유니코드에 정의된 문자는 이 이상(현재 21비트)이다.


 

(3) UTF-16

이 문제를 해결하기 위해서 등장한 것이 UTF-16이다. UTF-16에는 surrogate라는 개념이 등장한다. surrogate는 0xD800-0xDBFF 영역의 high surrogate와 0xDC00-0xDFF 영역의 low surrogate로 나뉘고 이 두 영역을 조합해서 하나의 문자(코드 포인트)를 만든다. surrogate를 사용하면 0x010000에서 0x10FFFF사이의 공간을 사용할 수 있다(현재까지 유니코드의 모든 문자를 표현할 수 있는 공간이다). 기본적인 인코딩 방식은 다음과 같다. 코드 포인트 A가 존재한다면 A에서 0x010000을 뺀다. 이 값은 0x00에서 0xFFFFF사이의 값, 즉 20비트이다. 그리고 이 20비트를 xxxxxxxxxxyyyyyyyyyy의 두 영역으로 나눈 후,

110110xxxxxxxxxx 110111yyyyyyyyyy

으로 인코딩한다.

참고로 윈도에서 유니코드라고 이야기하는 것은 UTF-16을 이야기한다. 메모장을 열어서 파일 저장하기 대화 상자를 보면 인코딩 항목을 볼 수 있는데 ANSI는 기존의 코드 페이지 방식(한글의 경우는 CP949 인코딩), 유니코드는 Little Endian UTF-16, 유니코드(big endian)은 Big Endian UTF-16, UTF-8은 UTF-8방식을 뜻한다.


 

(4) UTF-8

UCS-2와 UTF-16의 문제점은 지나치게 많은 공간이 필요하다는 점과 기존의 ASCII 체계와 호환성이 없다는 점이다. 첫번째 문제는 컴퓨터상에 존재하는 많은 글자들이 기존에는 1바이트로 표현할 수 있는 글자들인데 이 글자들을 위해서 2바이트 이상을 사용한다는 것은 너무 낭비라는 지적이다. 두번째 문제는 UCS-2나 UTF-16과 호환성을 위해서는 기존의 문서들을 모두 변환해야하는데 이 역시 문제라는 지적이다. 이 두가지 문제를 동시에 해결하는 인코딩 방식이 UTF-8이다.

UTF-8은 현재 유니코드 표현에 필요한 21비트를 1~4바이트에 걸쳐서 나누어서 표현한다. 다른 말로 하자면 UTF-8은 1바이트가 될 수도 있고, 2바이트가 될 수도 있고, 3바이트가 될 수도 있고, 4바이트가 될 수도 있다는 뜻이다.

기존의 ASCII 영역에 있던 글자들, 즉 7비트만 필요한 글자들(코드 포인트로는 00000 00000000 0xxxxxxx)은 1바이트인 0xxxxxxx으로 표현된다.

7비트 이상이 필요한 글자들은 차근 차근 바이트 수를 늘려간다.

코드 포인트 00000 00000yyy yyxxxxxxxx의 글자들은 110yyyyy 10xxxxxx으로,

코드 포인트 00000 zzzzyyyy yyxxxxxxxx의 글자들은 1110zzzz 10yyyyyy 10xxxxxx으로,

코드 포인트 uuuuu zzzzyyyy yyxxxxxxxx의 글자들은 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx으로 표현된다.

UTF-8은 XML문서를 위한 표준 인코딩으로 현재 가장 널리 쓰이는 인코딩 방식이다.

참고로 EUCKR과 CP949에 존재하던 문자들은 UTF-8에서 1~3바이트로 표현된다.


 

(5) 기타 인코딩

이 외에도 UTF-7, UTF-32 등의 인코딩 방식이 있다.


 

(6) 정리

유니코드는 전 세계의 모든 글자를 하나로 표현하려는 체계이다. 유니코드의 각 글자는 코드 포인트라는 숫자에 대응된다. 코드 포인트는 추상적인 개념으로 코드 포인트를 실제 바이트 형태로 표현하기 위해서는 인코딩이 필요하다.

UCS-2는 코드 포인트를 2바이트로 표현하는 인코딩 방식이다. UCS-2의 Endian을 명시하기 위해서 BOM이 사용된다.

UTF-16은 UCS-2의 모든 유니코드 문자를 표현할 수 없다는 점을 해결하기 위해서 등장했다. UTF-16은 surrogate라는 개념을 사용해서 코드 포인트의 영역을 두 곳에 분배한다.

UTF-8은 메모리 공간을 절약하고 기존의 ASCII 체계와 호환성을 위한 방식으로 현재 가장 널리 쓰이는 방식이다. UTF-8은 현재 21비트의 코드 포인트를 1~4바이트에 걸쳐서 표현한다. ASCII 영역의 문자들은 1바이트로 표현된다. ASCII 영역의 글자는 ASCII와 UTF-8에서 동일하게 표현된다.