UTF-16

16-bit UCS Transformation Format (16비트 국제 문자 집합 변환 포맷). 유니코드 문자 인코딩1). UTF-8바이트 기반 전송·저장을 효율적으로 할 수 있도록 만들어졌다면, UTF-16은 실제로 컴퓨터 내에서의 조작을 비교적 편리하게 하도록 만들어졌다.

그 특성 때문에 UTF-16은 많은 운영체제에서 유니코드를 내부적으로 표현하는 목적으로 흔히 쓰이며, 특히 마이크로소프트 윈도윈도2000 이래 전체 API가 완벽하게 UTF-16 기반이다.2) 또한 많은 프로그래밍 언어에서 "유니코드 문자열"이라고 할 때 실제로 메모리 상에 들어 있는 문자열이 UTF-16인 경우도 흔하다(파이썬, 자바 등).

구조

UTF-16은 16비트 워드를 기준으로 하는 인코딩으로, 크게 세 가지 경우로 나눌 수 있다:

  • 만약 코드포인트가 U+0000과 U+FFFF 사이이며, U+D800과 U+DFFF 사이에 속하지 않으면, 코드 포인트를 한 워드로 그대로 표현한다.
  • 만약 코드 포인트가 U+10000 이상이면,
    1. 코드 포인트에서 0x10000을 뺀다. 이 결과는 항상 20비트로 표현할 수 있다.
    2. 상위 10비트를 U+D800과 더해서 첫번째 워드로 한다.
    3. 하위 10비트를 U+DC00과 더해서 두번째 워드로 한다.
  • 만약 코드 포인트가 U+D800과 U+DFFF 사이에 속하면, 해당 문자는 표현할 수 없다.

여기서 볼 수 있듯 UTF-16은 기본 다국어 평면을 희생해서 더 많은 코드 포인트를 표현한다. (당연하지만 해당 코드 영역은 이 용도 말고 다른 용도로 쓸 수 없도록 고정되어 있다.) 이 영역을 서로게이트 영역(surrogate area)라고 하며, 상위 10비트를 저장하는 데 쓰이는 U+D800부터 U+DBFF까지를 "상위" 서로게이트 영역, 하위 10비트를 저장하는 데 쓰이는 U+DC00부터 U+DFFF까지를 "하위" 서로게이트 영역이라고 부른다.

서로게이트 영역에 들어 있는 문자를 사용하지 않는 모든 유니코드 문자열은 정확히 하나의 올바른 UTF-16 표현을 가지며, UTF-8과 마찬가지로 각 코드포인트의 처음에 올 수 있는 워드와 마지막에 올 수 있는 워드가 명확하기 때문에 워드 단위 문자열 작업이 간편하다. 다만 워드 순으로 정렬한 UTF-16 문자열들은 항상 코드 포인트 순서대로 사전식 정렬되지 않는다는 점은 UTF-8에는 없는 제약.

UCS-2

UTF-16의 옛날 버전으로, 아주 먼 옛날에 유니코드가 216자 밖에 존재하지 않았을 적에 정의된 문자 인코딩이다. 따라서 서로게이트 영역에 있는 문자는 UCS-2에 아예 나타날 수 없으며, U+10000을 넘는 문자들 역시 표현이 불가능하다. 따라서 기술적으로는 UTF-16의 부분집합이라 할 수 있다.

이 인코딩은 바이트로 표현할 때는 항상 빅엔디안을 쓰도록 되어 있었다. 빅 엔디안이 네트워크 바이트 순서임을 생각하면 이런 선택은 다분히 의도적이라 할 수 있다. 하지만 대부분의 구현이 UTF-16과 똑같은 방법으로 빅 엔디안, 리틀 엔디안, 그리고 바이트 순서 마크를 UCS-2에서도 구현하기 때문에 실질적으로는 (후술할) 바이트 순서 문제가 있다고 보는 게 옳다.

바이트 순서 마크

UTF-16은 기본적으로는 바이트 기반 전송·저장을 위해 만들어진 게 아니지만, 가끔씩 가다 보면 UTF-16을 바이트 기반으로 처리를 해야 할 때가 존재하긴 한다. 그런데 이렇게 되면 희대의 떡밥인 빅엔디안을 쓸까 리틀엔디안을 쓸까 하는 문제가 대두되는데, 그래서 유니코드가 내 놓은 방법이라는 것이 이렇다.

  • 유니코드 기본 다국어 평면 안에 있는 한 문자를 정해서 어떤 역할도 하지 않도록 정의한다. 이를 바이트순서마크(Byte Order Mark, BOM)라고 한다.
  • 해당 문자를 바이트 단위로 뒤집은 다른 문자를 어떤 용도로도 사용할 수 없도록 정의한다. (즉, 바이트가 뒤집힌 BOM은 유니코드 문자열에 나타날 수 없다고 선언한다.) 물론 BOM과 바이트가 뒤집힌 BOM은 다른 문자여야 한다.
  • 이제 모든 UTF-16 문자열 앞에 BOM을 붙이고, 만약 해당 문자열을 특정한 엔디안으로 다시 읽었을 때…
    • 첫 글자가 BOM이면 제대로 읽은 것이므로 BOM만 떼어 낸다.
    • 첫 글자가 바이트가 뒤집힌 BOM이면 반대로 읽은 것이므로 BOM을 떼어낸 뒤 나머지 글자들의 바이트를 뒤집는다.
    • 이도 저도 아닐 경우 이 문자열은 엔디안이 정해져 있지 않으므로 도박을 해야 한다. 다행히도 웬만해서는 한 시스템이 엔디안을 바꿔 가면서 UTF-16을 쓰는 경우는 없으므로, 승산 있는 도박이라 할 수 있다.

유니코드에서 실제로 사용하는 BOM 코드 포인트는 U+FEFF로, 이 문자가 다른 곳에 나타났을 때도 아무 영향을 끼치지 않도록 너비가 없고 줄바꿈이 불가능한 공백(ZWNBSP = Zero-width non-breaking space)으로 정의했다. 공백의 주된 목적은 적절한 너비를 주거나 줄바꿈을 할 위치를 선언하는 것인데 두 목적을 모두 없앴으니 사실상 아무 역할도 하지 않는 문자가 된 것이다(…).3) 따라서 BOM이 붙은 UTF-16 문자열을 두 개 갖다 이어도 별 문제가 발생하지는 않는다.

이 접근은 의도는 좋았을지 모르나 여러 가지로 문제가 있는데, 가장 큰 문제는 이 인코딩이 UTF-8과 공존했다는 것이다. UTF-8은 UTF-16과는 달리 BOM이 없어도 바이트 단위로 처리하는 데 전혀 문제가 없으며, UTF-16의 거의 모든 장점과 UTF-8 고유의 추가적인 장점을 고루 가지고 있다. 따라서 바이트 기반 전송을 할 거라면 UTF-16을 굳이 쓸 필요가 없다! 실제로 에서의 UTF-16 사용은 일찍이 사장된 상태이다. 만약 UTF-16을 저장 목적으로만 한정지었더라면 엔디안에 대한 정보를 인코딩하는 별개의 방법 하나를 (이를테면, ISO/IEC 5218같이 임의의 값을 정의해서라도) 만드는 것만으로 문제를 해결할 수 있었다. 그 밖에 바이트 순서 마크가 붙은 UTF-8가 양산되는 문제도 있었다(…).

1) The Unicode Standard, Version 6.0, Section 2.5 (pp. 27).
2) 사람 헷갈리게시리 여기서 UTF-16을 부르는 이름은 "Wide character"이다. 게다가 이 놈들은 활성 코드페이지에 대응하는 인코딩을 "ANSI" 인코딩이라고 심각하게 잘못 부르고 있다.
3) 그런데 여기에서 반전이 하나 있는데, 이 문자는 렌더링에는 아무 역할도 하지 않지만 여전히 "이 위치에서 텍스트가 나뉜다"라는 것을 나타내는 의미 자체는 유지하고 있다. 이 때문에 나중에 U+2060(WJ = Word joiner)라 하여 이 역할을 나타내는 문자가 별도로 추가되었다.

도쿠위키DokuWiki-custom(rev 9085d92e02)을 씁니다.
마지막 수정 2011-12-30 03:12 | 작성자 lifthrasiir