유니코드 1.x가 제정되었을 때는 믿거나 말거나 한글도 글자마디는 완성형 몇천 자만 지금과는 딴판의 영역에 등록되어 있었다. 단, 글자마디뿐만 아니라 옛한글을 포함한 자모도 자음 134개(물론 초성과 종성의 개수는 서로 다르고 따로 등록됨), 모음 66개가 등록되어서 이 자모들을 아래아한글 2.x가 한컴 2바이트 코드에서 그대로 채택해서 썼다. 한컴 2바이트 코드는 기존 조합형 코드와 유니코드를 적당히 짬뽕한 문자 코드였던 것이다.
유니코드는 첫 도입 배경이 저렇다 보니 “유니코드 = all 2바이트 단일 문자 체계 = wide character 문자 체계”라고 혼동하는 사람이 적지 않다. 그러나 이는 개념적으로 올바른 관계가 아니다. 2바이트 문자 체계로만 치자면 한컴 2바이트 코드도 이에 해당하며, 윈도 이외에 유닉스 계열 OS에서는 wide character는 16비트가 아니라 32비트인 경우도 있다. 왜 32비트이냐 하면 6만 5천여 개라는 집합 크기도 시간이 흐르다 보니 부족해졌기 때문이다.
1996년에 유니코드 2.0이 제정되면서 유니코드는 오늘날 우리가 아는 유니코드와 같은 뼈대가 갖춰졌다. 한번 등록한 문자는 이제부터 재배치나 제거 없이 영구불변으로 굳히기로 했으며, 이때 현대 한글 글자마디 11172자가 순서대로 고스란히 등록되었다. 2바이트 조합형처럼 비트 연산은 못 쓰지만 그래도 테이블 없이 뺄셈과 나눗셈, 나머지 연산으로 한글의 자소 정보를 얻을 수 있으니, 과거 조합형 한글 코드의 장점을 유니코드에서도 물려받은 셈이다.
외국 위원들의 반대를 무릅쓰고 한글에다 유니코드의 금싸라기 영역을 이만치 할당 받기 위해 한국 위원회 사람들이 애를 많이 썼다. 사실 확장완성형도 유니코드 등록 근거를 대기 위해 급히 만들어진 것이라고 한다.
이와 더불어 유니코드 2.0에서는 중요한 개념 내지 꼼수가 추가로 도입되었는데, 바로 surrogate이다.
16비트 공간 중 일부 영역은 그대로 쓰지 말고 따로 떼어 내서 두 글자를 뭉쳐서 더 많은 종류의 글자를 표현하는 데 쓰자는 것이다.
0xD800부터 0xDBFF는 high surrogat이고, 0xDC00부터 0xDFFF까지는 low surrogate이다. high는 언제나 앞에, low는 언제나 뒤에 오는 식으로 역할이 딱 고정되어 있다. 1024개의 앞짝과 1024개의 뒷짝이 만나니, 총 2048개의 독립 글자를 희생하여 2의 20승, 약 100만여 개의 글자 공간을 추가로 확보할 수 있게 된다. 요컨대 유니코드에 U+D800 같은 글자는 없고, U+D7FF 다음에는 곧바로 U+E000이 이어진다. 그 대신 0xD800과 0xDC00이 만나면 U+10000이라는 surrogate, 즉 확장 평면이 시작될 뿐이다. 예전의 16비트 단일 영역은 '기본 다국어 평면'(BMP)이라고 불리게 되었다.
비록 과거의 1바이트/2바이트 혼합 체계보다는 사정이 훨씬 낫고 문자열 처리가 수월한 건 사실이지만, 어쨌든 유니코드도 확장으로 인해 2바이트 단일 표현(UCS2)이라는 깔끔한 장점은 포기해야 하게 되었다. 이쯤 되니 유니코드라는 개념에서 문자 코드와 인코딩을 구분해서 표현해야 할 필요가 생겼다.
유니코드 자체는 UCS, 즉 universal character set이라 불리는 단일 문자 집합이다. 얘는 '가'라는 한글 글자에다가 U+AC00이라는 코드값을 부여해 줄 뿐, 그 코드값이 메모리나 파일에 어떻게 표현되는지에 대해서는 규정하지 않는다. 이를 표현하는 방식이 바로 Unicode transfer format, 즉 인코딩이 된다.
모든 유니코드 번호를 아무 뒤끝 없이 32비트 정수 하나로 균일하게 표현하면 그것은 UTF32이다. 아까 wide character가 4바이트인 플랫폼은 바로 UTF32를 구현하는 자료형인 것이다. 가장 깔끔하고 공간도 넉넉하고 모든 글자를 하나씩 쉽게 접근할 수 있지만 한 글자가 4바이트나 차지하여 메모리 낭비가 심하다.
BMP 영역에 있는 놈은 종전대로 16비트 정수 하나로 표현하고 확장 평면에 있는 놈만 surrogate 두 개로 쪼개서 표현하는 방식은 UTF16이라고 불린다. UCS2를 개념적으로 확장한 것이기 때문에 처음부터 2바이트 wide character를 표방하며 개발된 Windows 플랫폼이 매우 사랑하는 방식이다. 비록 Windows의 wchar_t는 이제 유니코드 코드 포인트 하나를 온전히 표현할 수 있을 정도로 충분히 크지 못한데도 말이다.
끝으로, 그 이름도 유명한 UTF8이 있다. 얘는 U+007F 안에 드는 전통적인 알파벳· 숫자, 제어 문자들은 1바이트로 유지하고, 나머지 BMP 영역의 외국어들은 2~3바이트로 표현하고 surrogate는 BMP와 동일한 4바이트로 늘여 표현하는 진정한 multibyte 인코딩이다.
n째 문자를 찾는 무작위 접근은 안 되겠지만, Windows NT처럼 커널을 처음부터 16비트 문자 단위로 설계하지 않고도 기존 8비트 문자 시스템에서 유니코드를 단절감 없이 표현할 수 있다는 엄청난 장점이 있다. CPU의 엔디언(비트 배열 순서)의 영향을 받지 않으며, tail byte가 기존 영문· 숫자와 충돌을 일으키지도 않는다는 점도 좋고 말이다.
그래서 얘는 웹에서 매우 사랑받고 있고 윈도 이외의 플랫폼에서는 파일뿐만 아니라 메모리 저장용으로도 UTF8이 즐겨 쓰인다. 사실 윈도만 이례적으로 UTF16을 너무 사랑하고 있는 것이고.. ㅎㅎ
여담이지만 UTF32, 16, 8 중 유니코드의 문자 집합이 먼 미래에 또 확장되어야 할 때, 공간이 가장 넉넉하게 남아 있는 놈은 두 말할 나위 없이 UTF32이다. UTF8이야 애초부터 들쭉날쭉 가변 길이 인코딩을 표방했기 때문에 한 글자당 4바이트보다 더 긴 5~6바이트까지 약간 더 확장할 여지가 있다. 지금 당장은 그것까지는 쓰이지 않지만 말이다.
하지만 UTF16은 유일하게 확장의 여지가 전혀 없다. 지금 surrogate 영역이 다 차 버리면 surrogate의 surrogate라도 또 만들지 않는 이상 답이 없다. 그리고 그런 헛짓을 할 바에야 차라리 UTF16을 폐기하고 UTF8 아니면 UTF32로 갈아타는 게 낫지..;; 이런 미묘한 면모가 있다.
다만, 한글 표현의 관점에서는 메모리가 가장 적게 드는 인코딩이 UTF16이라는 것도 감안할 점이다. 현대 한글 글자마디의 경우 UTF8은 3바이트, UTF32는 4바이트이지만 UTF16은 2바이트다. 초-중-종성이 모두 갖춰진 옛한글이라면 UTF8과 UTF32는 각각 9바이트, 12바이트를 차지하지만 UTF16은 6바이트이니 차이가 더 벌어지게 된다. 유니코드에는 한글이 완성형(글자마디+호환용 자모) 방식과 조합형(세벌 한글 자모) 방식이 모두 등록되었으며 완성형 방식도 11172자가 순서대로 모두 등록되었으니, 예전의 조합형· 완성형 논쟁을 완전히 종결짓는 데 성공했다.
Windows NT도 처음에 개발될 때 문자 처리 단위를 wide로 무리해서 넓히느라 고생하지 말고, 그냥 UTF8만 도입했으면 어땠을까 싶지만 이제 와서는 다 지난 일이다. 아무래도 UTF8보다야 UTF16이 컴퓨터의 입장에서는 더 빠르고 간편하게 각각의 문자를 인식할 수 있으며, 이것이 메모리와 성능 소모면에서 UTF32와 UTF8 사이의 완충지대 역할을 해 온 건 부인할 수 없기 때문이다. 덕분에 Windows API의 관점에서는 UTF8조차도 native 인코딩인 유니코드 UTF16으로부터 '변환'되어야 하는 여러 multibyte 인코딩 중의 하나일 뿐이다~! ㅎㅎ
옛날에 인터넷 익스플로러의 버전이 5~6이던 시절엔 한글로 된 URL이 인식이 잘 안 돼서 맨날 “URL을 UTF8로 보냄” 옵션을 끄라는 지시가 팁처럼 공유되곤 했다. 당시엔 Unicode-aware하지 않은 웹 서버가 아직 많아서 말이다. 오늘날로 치면 UAC(사용자 계정 컨트롤)를 끄거나 팝업 창 허용 기능을 사용하는 것과 비슷한 편법이다.
하지만 요즘은 UTF8 방식 URL이 표준으로 정착한 지 오래다. 호환성을 중요시하는 IE에나 그런 옵션이 있지, 크롬 같은 브라우저는 선택의 여지 없이 무조건 UTF8이기도 하고 말이다.
유니코드는 문자 처리 기술의 발전과 함께 더욱 덩치가 커졌으며, 한편으로 더욱 복잡해지고 지저분해지고 있다.
UTF32가 아닌 인코딩으로는 어차피 메모리 배열 인덱스만으로 실제 글자 인덱스를 얻을 수 없는 것은 물론이거니와, 메모리 상으로 존재하는 글자 코드 포인트 수와, 화면에 출력되는 글자의 수도 일대일 대응이 전혀 이뤄지지 않게 되었다. 옛한글이라는 것도 유니코드 관련 기술이 방대해지면서 덤으로 같이 처리 가능해졌을 뿐이다. 진짜 미치도록 복잡한 문자에 비하면 옛한글 쯤이야 양반이지... 동일한 글자를 나타내는 방법이 여러 가지 존재하게 되어서 정규화라는 것도 필요해졌다.
코드 포인트 값만으로 정렬을 하는 것 역시 상당수 의미를 잃었다. 옛한글 자모는 유니코드 5.2 때 한양 PUA 비표준에만 존재하던 것들이 추가 등록되었는데, 이것들의 코드 포인트상의 순서를 따지는 건 부질없는 짓이다. 가뜩이나 부족한 BMP 공간의 여러 틈새에 산발적으로 간신히 등록된 것 자체를 감지덕지해야 할 판이다.
한자는 더욱 가관이다. 유니코드에서 공간을 압도적으로 제일 많이 차지하고 있는 문자인 건 두 말할 나위도 없거니와.. BMP 영역에 이미 등록돼 있고 호환용 같은 이유도 없는데 작업자의 실수로 인해 나중에 surrogate 확장 평면에 중복 등록된 글자도 있고, 일본 문자 코드의 실수 때문에 문헌에 전혀 등장한 적이 없는 유령 한자가 등재돼 있기도 하다.
이것이 오늘날의 컴퓨터 문명을 지배하고 있는 가장 원초적인 규범에 속하는 유니코드의 현실이다. ㅎㅎ
수십 년 주기로 유니코드를 전면 reset하고 코드값을 재정리하면 어떨까 싶지만 그건 기존 컴퓨터 문명이 핵 전쟁 같은 걸로 폭삭 없어지지 않는 한, 상상 속에서나 가능한 일일 것이다.
Posted by 사무엘