한글인코딩

in #ram7 years ago (edited)

한글을 표현할 수 있는 문자 인코딩 방식에는 무엇이 있을까? 각각의 장단점은?

한글 인코딩 이야기를 하기 전에 ASCII 이야기부터 해보자


ASCII 는 꽤나 추상적이고 철학적인 “글자”라는 개념을 컴퓨터로 표현, 저장하기 위한 방법이다. 다시 말해 글자 하나 하나를 숫자와 짝지어 둔 일종의 표라는 이야기다. “A는 65라고 컴퓨 터에서 저장하고, B는 66이라고 저장하자” 이런식으로 미리 약속을 한 것이다. 그럼 ASCII 코드는 몇 비트를 사용하는 코드 체계인가? 7비트다. 알파벳과 기타 필요 한 문자들을 표현하는데 128글자(2^7)면 충분했기 때문이다. 하지만 대부분 컴퓨터는 기본 저장 단위로 8비트를 사용한다. 이 남는 1비트 때문에 혼돈과 가능성의 시대가 열린다.

약간 골치가 아프지만 잠시 비트 단위 이야기를 해보자


앞에서도 이야기했듯이 우리는 일반적으로 1바이트=8비트인 컴퓨터를 쓰기 때문에 ASCII 코드를 사용하면 1 비트가 남는다 (물론 1바이트는 8비트가 아닌 컴퓨터도 있다). 다시 말해 8비트 체 계에서 ASCII 코드는 00000000(0)~01111111(127)까지만 쓰기 때문에 1000 0000 (128) ~ 1111 1111 (255) 까지의 영역이 남는다는 뜻이다. ASCII 코드에 포 함되지 않은 글자를 사용하는 나라들이 이 영역에 자신들의 글자를 채워넣기 시작했다. 문제는 이런 과정이 통제된 방식이 아닌 각 나라들의 독자적인 방법으로 동시에 진행됐다는 점이다. 문자와 코드 사이의 관계가 ASCII 만 존재 할 때보다 복잡해졌지만,

이 문제는 멀티 바이트 코드가 등장하면서 더욱 악화된다.


안타깝게도 한글을 포함해서 몇몇 글자 체계들(주로 중국, 일본, 한국을 통틀어서 CJK라고 부른다)은 기존의 8비트 코드 체계로는 모든 글자를 표현할 수가 없었다. 하지만 한 글자를 표현하기 위해서 2바이트를 사용한다면 2^16=65536 글자라는 넓은 공간을 사용할 수 있고, 한글 역시 모두 표현할 수가 있다. 이런 생각으로 등장한 것이 멀티 바이트 코드다.

128~65535라는 영역이 한글을 위한 공간으로 주어졌지만 이곳에 한글을 어떻게 할당하느냐는 문제가 남았다. 여기에서 대해서 두가지 의견이 존재했다. 첫번째는 한글 한글자 한글자를 독립된 개념으로 생각하고 각 글자에 코드를 부여했다는 의미에서 “완성형”이라고 하고, 다른 하나는 한글의 창제 원리인 초성, 중성, 종성의 개념을 살려서 초성, 중성, 종성에게 각각 5비트를 할당하자는 의미에서 "조합형"이라고 한다.

완성형과 조합형은


각자의 장단점을 가지고 꽤나 치열했던 코드논쟁을 일으켰다. 조합형은 한글의 창제 원리를 고스란히 담고 있고, 조합이라는 방식 덕에 모든 한글을 표현할 수 있다는 장점이 있다. 하지만 코드를 글자로 변환하기 위해서는 2바이트를 비트 단위로 쪼개서 해석을 해야하기 때문에 처리상의 부담이 있었다. 반면 완성형은 숫자가 글자를 그대로 의미하기 때문에 비트 단위 해석의 부담은 없었지 만 한글의 창제 원리를 무시하고 마치 한글을 한자처럼 취급한다는 단점이 있었다. 오히려 완성형을 자소 단위로 쪼개기 위해서는 추가적인 해석 부담이 있다. 더군다나 뒤에 더 이야기가 되겠지만 실제로는 완성형에서 한글을 위해 할당된 영역이 128~65535가 아닌 제한된 영역이기 때문에 모든 한글을 표현하지 못하는 문제가 발생했다.
당시 전산계에 있던 사람들 사이에서는 조합형의 우월성에 많은 점수를 주었지만, 완성형 방식이 표준으로 채택되면서 꽤나 시끄러웠던 한글 코드 논란은 일단락 되었다.
안타깝지만 조합형은 표준으로 채택되지 않았기 때문에 이제는 완성형에 대해서만 이야기하자. .

표준 완성형 인코딩 방식의 다른 이름은 EUCKR이다


EUCKR은 KS X 1003과 KS X 1001으로 구성된다. KS X 1003은 역슬래쉬 대신에 원화표시를 사용한다는 점을 제외하면 아스키 코드의 문자들과 같다. KS X 1001은 한글과 그림 문자, 한자 등을 포함한다. EUCKR은 127이하에는 KS X 1003을, 128이상에는 KS X 1001을 할당했다. 128이상이라고 하지만 실제로 EUCKR이 사용한 공간은 상위바이트 161~254, 하위바이트 161~254뿐이다. 더구나 KS X 1001에는 한글 외에도 그림 문자와 한자 등의 글자가 많기 때문에 그렇지 않아도 작은 이 영역에 들어가지 못한 한글이 생겼다. 바로 “똠”이나 “뷁” 등이다. PC 통신 세대라면 어떤 게시물들의 글자가 보이지 않았던 경험이 있을 것이다. 그 글자들이 바로 KS X 1001, 그리고 EUCKR에서 제외된 글자들이다.
자주 사용되지 않는 몇몇 글자들을 EUCKR에서 사용할 수 없다는 점이 어떤 사람들에게는 별 문제가 아니었지만 어떤 사람들에게는 큰 문제였다. 예를 들어 고어를 많이 사용하는 국어학자라거나, 채팅이나 소설에서 독특한 표현을 사용하는 사람들에게 EUCKR은 족쇄나 마찬가지였다.

이때 등장한 것이 마이크로소프트의 CP949


즉, 코드 페이지 949이다. CP949는 마이크로소프트가 KS X 1001에 없는 한글 8822글자를 추가해서 EUCKR을 확장한 완성형 인코딩 방식이다. 128이상의 영역 중 EUCKR이 사용하지 않던 영역에 이 8822글자를 할당했다. 그래서 CP949확장 완성형 또는 통합형 한글 코드라고도 불리운다. 마이크로소프트에서는 CP949ks_c_5601-1987이라고도 부른다.

EUCKR vs CP949


EUCKRCP949는 혼동되는 경우가 있지만 둘은 엄연히 다른 인코딩 방식이다. 둘이 혼동되는 이유는 둘다 완성형 방식이고 CP949EUCKR을 완전히 포함하기 때문일 것이다. 우리가 윈도에서 사용하는 한글은 EUCKR아닌 CP949로 표현된다 (물론 윈도는 내부적으로는 예전부터 유니코드를 사용하고 있다). 윈도 메모장에서 “똠방각하”를 작성한 후 특별한 과정없이 텍스트 파일로 저장할 수가 있다. 윈도가 CP949를 사용하기 때문이다.

그런데 Java에서는


CP949MS949를 다르게 취급한다. CP949는 IBM에서 처음 지정한 코드 페이지(sun.nio.cs.ext.IBM949)가 기준이고 Microsoft가 제정한 확장 완성형은 MS949(sun.nio.cs.ext.MS949)가 기준이다. 그러므로 Java에서는 CP949EUC-KR이 사실상 같으며, 확장 완성형을 사용하기 위해서는 MS949로 지정해야 한다.


정리를 해보자


유니코드가 강림하기 전 세상에서 글자를 표현하기 위한 코드는 127이하의 영역과 128이상의 영역이 있었다. 128이상의 영역은 각 나라의 언어별로 채워졌고, 한글을 비롯한 몇몇 글자 체계에서는 2바이트 이상을 사용했다. 이 128이상 영역의 글자를 해석하는 방식에 대한 약속이 코드 페이지이다. 128이상 영역에 한글(을 비릇한 그림 문자, 한자)을 할당하기 위해서 제안된 방식으로 완성형, 조합형이 있었고 완성형이 표준으로 채택됐다. 완성형의 표준 방식은 EUCKR이었고, EUCKR은 모든 한글을 표현하지 못했다. 마이크로소프트에서 EUCKR에 포함 되지 않은 한글 8822자를 EUCKR이 사용하지 않던 영역에 할당했고 이를 확장 완성형 또는 CP949라고 부른다.

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

세상의 모든 글자를 하나의 체계로 표현하기 위해서 등장한 것이 유니코드다.


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

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인지 FF FE 인지에 따라 나머지 부분을 해석하는 방식을 다르게하면 된다. FE FF라면 ‘A’가 00 48이고 FF FE라면 ‘A’가 48 00 으로 저장됐다고 생각하면 된다.
BOM의 등장만 해도 문제가 복잡한데, 더 심각한 문제는 모든 UCS-2 방식의 문자열들이 BOM을 달고 있지는 않다는 점이다. 동일한 시스템에서만 문자열을 주고 받는다면 문제가 없지만 다른 바이트 순서를 사용하는 시스템 간에 문자열을 주고 받을 때는 분명히 문제가 된다.
이보다 더 심각한 UCS-2의 문제는 UCS-2가 2바이트를 사용하기 때문에 표현할 수 있는 글자의 한계가 65,536글자라는 점이다. 유니코드에 정의된 문자는 이 이상(현재 21비트)이다.

UTF-16


이 문제를 해결하기 위해서 등장한 것이 UTF-16이다. UTF-16에는 surrogate라는 개념이 등장한다. surrogate는 0xD800-0xDBFF 영역의 high surrogate와 0xDC00- 0xDFF 영역의 low surrogate로 나뉘고 이 두 영역을 조합해서 하나의 문자(코드 포 인트)를 만든다. surrogate를 사용하면 0×010000에서 0x10FFFF사이의 공간을 사용할 수 있다(현재까지 유니코드의 모든 문자를 표현할 수 있는 공간이다). 기본적인 인코딩 방식은 다음과 같다. 코드 포인트 A가 존재한다면 A에서 0×010000을 뺀다. 이 값은 0×00에서 0xFFFFF사이의 값, 즉 20비트이다. 그리고 이 20비트를 xxxx xxxx xxyy yyyy yyyy 의 두 영역으로 나눈 후,
1101 10xx xxxx xxxx 1101 11yy yyyy yyyy
으로 인코딩한다.
참고로 윈도에서 유니코드라고 이야기하는 것은 UTF-16을 이야기한다. 메모장을 열어서 파일 저장하기 대화 상자를 보면 인코딩 항목을 볼 수 있는데 ANSI는 기존의 코드 페이지 방식(한글의 경우는 CP949 인코딩), 유니코드는 Little Endian UTF-16, 유니코드(big endian)은 Big Endian UTF-16, UTF-8은 UTF-8방식을 뜻한다.

UTF-8


UCS-2UTF-16의 문제점은 지나치게 많은 공간이 필요하다는 점과 기존의 ASCII 체계와 호환성이 없다는 점이다. 첫번째 문제는 컴퓨터상에 존재하는 많은 글자들이 기존에는 1바이트로 표현할 수 있는 글자들인데 이 글자들을 위해서 2바이트 이상을 사용한다는 것은 너무 낭비라는 지적이다. 두번째 문제는 UCS-2UTF-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문서를 위한 표준 인코딩으로 현재 가장 널리 쓰이는 인코딩 방식이다.
참고로 EUCKRCP949에 존재하던 문자들은 UTF-8에서 1~3바이트로 표현된다.

이 외에도


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

Coin Marketplace

STEEM 0.26
TRX 0.20
JST 0.038
BTC 95099.53
ETH 3571.49
USDT 1.00
SBD 3.82