인코딩과 문자집합: Unicode

이전 포스팅에서 인코딩과 문자집합; encoding and charset 그리고 그 자체를 저장하기 위한 데이터 형에 대해서 간략히 설명했었는데, 실제로 구현된 예를 들어보도록 하겠음

앞에서도 말했듯이,

  • 특정 언어가 갖는 문자들을 숫자값들로 변환하는게 문자집합; charset의 개념
  • 캐릭터의 숫자값들을 비트 패턴으로 저장할 수 있게 해주는게 인코딩
  • 그리고 인코딩된 비트들을 저장하는게 기반 데이터 타입 (C/C++의 char 혹은 wchar_t)

로 되어 있다.

지구 상에 존재하는 언어가 몇 개인지는 알지 못하지만, 그걸 표현하기 위한 문자집합의 수가 매우매우 많다는 것은 간단한 예를 들 수 있다. *nix 시스템에서 locale로 사용 가능한 것들을 찾아보거나, MS Windows 시스템에서 추가 가능한 입력 문자집합만 찾아봐도 된다. 이런 문제를 해결하기 위한 노력이 결실을 맺은 것이 Unicode 다.

현존하는 모든 언어 체계의 문자집합 – 심지어 중세 이전의 문자들까지도 표현하는 – 으로써 현재 Unicode란 표준이 존재하며, ‘알파벳 대문자 A = U+0041’ 하는 16진수 표현으로(U+이후가 16진수) 각 문자들에 값을 할당하고 있다(캐릭터셋이란 소리). 그리고 이런 문자들의 숫자값을 실제 비트로 바꾸기 위한 방법들이 여러가지가 존재한다. 적어도 지금 사용되고 있는 것만해도 4종이 넘는다.

  • UTF-7: 각 byte의 최상위 1 bit을 쓰지 않고도 unicode를 저장하기 위한 포맷. 1개 이상의 byte로 쪼개서 표현
  • UTF-8: 각각의 unicode 문자를 1개 이상의 byte로 쪼개서 표현한다. ASCII에 해당하는 처음 128개가 완전히 동일하게 표현된다 – 그러나 한글의 경우 한 문자당 3바이트 이상이 소요되는 가변길이 표현.1
  • UTF-16: 저장하는 단위가 2bytes(보통 wchar_t의 구현)이다. 역시 가변길이다. UCS2의 super set이 됨
  • UCS2: 저장하는 단위가 2bytes 고정길이이다. 따라서 모든 문자를 표현할 수는 없다.2

일단 이것만으로도 어제 쓴 글에서 飛烏가 질문했고, 피앙과 수원이형이 답변한 “하나의 문자집합; character set이 다수의 인코딩을 가질 수 있다” 라는 사실을 확인할 수 있다. (덤으로 동아시아 언어들의 문자집합의 경우 대부분 이런 경우를 볼 수 있다.)

실제 예를 들어보자. “한글"이라는 문자열의 경우 각 글자가 Unicode에서 숫자값이 각각 U+D55C, U+AE00 로 되어있다. 이를 UCS2로 _인코딩_하면, 0x 5C D5 00 AE 가 된다. (little endian) 반면에 UTF-8 인코딩을 사용하면, 0x ed 95 9c ea b8 80 가 된다. (각 글자가 3 bytes로) 즉 원본 표현은 하나지만, 인코딩 된 표현은 여러 개인 것이다.

위에서 구분한 것 중에 알 필요가 있는 것은 두 부류라고 생각한다. 바로 UTF-8과 UCS2/UTF-16이 그 것인데, 그렇게 고른 이유는,

  • 많은 *nix 시스템, web, xml은 UTF-8 을 주요 인코딩으로 사용한다.
  • MS Windows는 UCS2, Java 2/Java 5는 UCS2/UTF-16에 의존하고 있다.

이 두 가지다. 덤으로 사랑해마지 않는 python이 UTF-8을…

아마 프로그래밍을 하다보면 – 한가지 OS/플랫폼만 쓴다고 해도 아마 – 이 두 가지 형태 혹은 유사한 형태들을 모두 보게 될 것이다 – 덤으로 (일반적으로)2 bytes 길이 안에서 해당 언어의 모든 문자를 표현하는 legacy 문자집합 + 인코딩들도.

Berryz WebShare3의 경우 이 모두를 사용하는 경우인데, Windows의 파일시스템과 관련된 부분들은 UCS2를 사용하고 HTTP를 통해 연결된 부분은 모두 UTF-8을 사용한다. (한 마디로 같은 의미를 갖는 문자열들을 저장하고 있는데 Windows 쪽에 보여줄 때는 UCS2로 된 것을, 웹 쪽에는 UTF-8으로 된 것을 보여준다는 의미)

이상의 내용을 정리해보면, 하나의 문자집합을 숫자로 표현한 후 이를 다시 이진데이터(bit패턴)으로 표현하는 방법은 하나 이상일 수 있다는 것, 그리고 각각이 갖는 특성들 때문에 각 응용에 맞는게 선택되어 사용되고 있다는 것이다.

다음 아티클 – 언제 쓸지는 (…) – 에서는 실제로 이 표현들이 char / wchar_t에 쪼개어져 담겨있는 것을 어떻게 다루는지 간략히 소개해보려고 한다.


  1. ASCII를 아무런 변환없이 쓸 수 있다는 점 때문이기도하고, NULL 문자가 나타나면 무조건 NULL(U+0000)이기도 해서 UNIX 시스템들에 광범위하게 채용되고 있다. 그리고 웹에서의 표준 인코딩으로 자리 잡아가고 있으며, XML의 표준 인코딩 Python 프로그래밍 언어의 내부 포맷이기도 함 ↩︎

  2. MS Windows의 파일 시스템, 레지스트리 등에 사용되는 포맷이다(Win98이후). 고정길이이기 때문에 BMP의 65536개의 숫자값만 표현할 수 있어서 현존하는 모든 Unicode 문자가 표현될 수 없다(대부분 표현된다 정도). 다만 고정길이이기 때문에 갖는 장점들이 있다. 다국어 프로그래밍을 좀 해봤다는 사람들도 이 UCS2와 UTF-16을 착각하는 경우가 왕왕있다. 그렇지만 가변길이 인코딩인지 여부로 구분을 해줘야 한다 :$ ↩︎

  3. 지금은 더 이상 배포하고 있지 않다. Windows PC에서 실행하면 다른 사람이 접근해서 연동한 폴더의 파일들을 접근할 수 있게 해주는 웹 서버 프로그램이었고, 여기서 내부 표현은 UTF-16, 웹 서비스 응답은 모두 UTF-8을 사용했다. 유사하게, .zip, .rar 다운로드 플러그인도 내부 표현은 동일하게 UTF-16 형식을 썼고, 외부 표현 형식은 가능한 RAR의 경우 UCS2 +하위 호환을 위한 naive한 인코딩을 동시에 썼다. ↩︎