유니코드 문자열과 문자열 인코딩

컴퓨터는 어떻게 글자를 표현할까

컴퓨터가 처리하는 모든 데이터는 궁극적으로 2진수로 표현되는 숫자값이다. 따라서 컴퓨터가 문자를 처리하도록 하기 위해서는 각각의 글자에 고유한 번호를 부여하여 특정한 숫자값과 특정한 글자를 1:1 로 맵핑하는 것이다. 이러한 문자 맵에서 가장 잘 알려져 있는 것이 아스키(ASCII) 코드이다.

아스키코드는 128개의 알파벳 문자 및 숫자와 문장부호, 몇 가지 괄호와 연산자 문자 그리고 프린터 출력에 필요한 제어 문자등을 정리한 코드 체계이다. 128개의 글자를 포함하고 있기 때문에 (물론 그 중에는 눈에 보이지 않는 글자도 있다.) 이 가지수는 모두 2의 7제곱이며 따라서 1바이트 (8자리) 2진수 값 하나가 문자 하나를 표현하는데 사용될 수 있다. C언어에서는 문자 하나를 표현하는데 char 라는 타입을 쓴다. 사실 이 타입은 아스키코드를 상정한 타입으로 실질적으로는 부호없는 1바이트 정수형이다. 그래서 char 타입끼리는 더하거나 빼는 연산이 가능하다. 실제로 특정한 문자를 대문자로 변환하는 함수는 다음과 같이 작성할 수 있다.

char toUpperCharacter(char lower) {
  char upper = lower;
  if (lower >= 'a' && lower <= 'z'){
    upper = 'A' + (lower - 'a');
  }
  return upper;
}

아스키 코드가 제정된 이후에 추가적인 몇 가지 특수 문자들을 표현하기 위해 확장 아스키코드가 추가로 제정되었고, 128~255사이의 구간에 배정되었다. 이렇게해서 아스키코드를 사용해서 문자를 표현하고 처리하는 것으로 모두들 행복하게 살았으면 좋겠지만….

비라틴 언어의 표현

아스키코드는 1바이트의 정수값을 사용한다고 했다. 여기서 바이트의 크기는 결국 하나의 값의 해상도에 비유된다. 즉 1바이트는 8개의 비트이고 0~255까지 256가지의 값을 표현할 수 있는 셈이다. 아스키코드 전체가 256자로 구성되다보니 1바이트 값 1개면 다른 글자와 혼동되지 않는 독립적인 문자 하나를 표현하기에 부족함이 없다.

문제는 영어문자를 사용하지 않는 국가들에서도 컴퓨터를 사용하면서, 각 국가의 문자를 표현하는 방법이 필요하게 되었다는 점이다. 예를 들어 일본에서는 확장 아스키 코드 영역에 가타카나 문자를 넣어 쓰는 등의 방법을 사용하거나(히라가나는 둥근 모양이 많아서 당시 기술로는 콘솔에서 표현하기가 어려웠다.) 하는 방법을 썼다. 하지만 이 방법으로는 한자를 표현할 공간은 턱없이 부족했다. 그래서 2바이트 문자를 도입하기 시작했다. 어떤 바이트의 첫 비트가 0이면 해당 바이트는 1바이트짜리 아스키 문자 1개로 해석하고, 1이면 그 뒤 바이트까지 연결해서 확장된 코드에 대응하는 문자로 해석하는 식이다.

중국, 일본, 한국등의 글자가 많이 필요한 국가에서 이 방식을 채택해서 문자를 표현하고자 했고, 그에 따라 각 국가는 자국어의 문자를 표현하기 위한 코드 테이블을 별도로 제정하였다. (물론 이 때 한국은 완성형 한글이나 조합형 한글이냐를 두고 의견이 분분하고 어쩌고…하는 일들이 있었다.)

2바이트는 8+8, 즉 16자리 2진수 값이며 이는 65,536가지 값을 표현할 수 있는 단위가 된다. 한글의 모든 자모 조합이 11,172가지이므로 그러면 모든 조합이 여기에 들어가네? 라고 생각했지만, 현실은 그렇지 않았다. 아스키 코드와의 충돌을 피하기 위해 두 바이트 모두 뒤쪽 절반의 비트만을 사용해야 했고, 그 와중에 제어용 비트들은 건드릴 수 없기 때문에 2바이트를 쓰더라도 실제로 가용한 글자수는 8,836자 밖에 되지 않는데, 우리 나라 역시 한자를 사용하기 때문에 상용한자를 위한 공간도 왕창 배정해야 했기에 실제로 표준 완성형에서 지원가능한 글자는 2350자 밖에 되지 않았다. 물론 이 당시 컴퓨터 성능이나 용량측면에서 어쩔 수 없는 부분도 있지만, 2350자는 표준어만 표기하더라도 부족한 글자였기에 당시에 거센 비판과 반발을 샀다.

어쨌거나 비 영어권에서는 이렇게 멀티바이트 문자처리를 이용해서 자국어를 표현하는 방법들을 만들어 내어서 모두가 행복하게 지낼 수 있을 줄 알았는데, 아직 문제는 여전히 남아있었다. 바로 국가간 정보의 호환이다. 멀티바이트 문자처리는 기본적으로 아스키코드와는 호환되지만, 확장한 영역내의 코드값에는 국가마다 다른 문자가 할당되었다. 따라서 다른 국가에서 사용하는 코드 페이지를 기준으로 생성된 문자열은 다른 국가에서는 올바르게 표현되지 않았다.

이 문제를 해결하기 위해서는 모든 언어에서 사용하는 문자들을 하나의 체계에서 모두 독립적인 코드를 부여할 수 있는, 매우 거대한 코드 테이블이 필요하게 되었고 이를 위해서 모든 문자를 표현할 수 있는 단일 코드 체계를 제정하기 위한 움직임이 일어나기 시작했다. 이것이 바로 유니코드이다.

유니코드는 최초 4바이트 크기로 제정되었다가 이후 6바이트 체계로 확장되었다. 6바이트 크기라는 것은 한 글자를 구분하는 고유 값이 2의 48승(6바이트는 48비트) 범위를 가질 수 있는 크기라는 것이다. 이를 십진수로 확장하면 281,474,976,710,656이며, 이는 지구상에 존재하는 모든 문자를 담고도 남고, 아마 우리 은하계에 존재할 수 있는 모든 문자를 다 포함할 수도 있는 방대한 영역이다.

인코딩

유니코드는 여러 종류가 있는 것이 아니라, 국가별로 자국어의 문자를 표현하기 위해 사용하는 코드표에서 코드값이 같은데 국가마다 다른 글자를 표현하게 되는 문제를 방지하기 위해서 그냥 모든 언어의 문자표를 다 합쳐서 정리한 것이라고 보면 된다. 그리고 현대의 대부분의 OS는 내부적으로 이 유니코드를 사용한다. 그런데 인코딩은 왜 필요하며, 어떻게 사용하는 것일까?

인코딩(encoding)이라는 용어부터 살펴보자. 인코딩은 “원래의 raw 데이터를 특정한 규격에 맞게 변환한다”는 뜻이다. 흔히 동영상 데이터를 mp4 등의 포맷으로 압축할 때 인코딩한다는 용어를 쓰는데, 압축포맷에 맞도록 원래의 커다란 동영상 데이터를 정리한다는 의미로 받아들이면 된다. 그렇다면 문자열에서 인코딩은 왜 필요할까? 유니코드를 지원하는 프로그램 혹은 프로그래밍 언어라면 내부적으로는 문자열을 유니코드로 처리하면 된다. 대신에 인코딩은 유니코드 포인트의 연속체인 문자열을 프로그램 외부와 교환하기 위해 어떤 공통된 규격으로 만들기 위해 사용한다. 프로그램 외부에서 문자열을 읽어들이거나 내보낼 때 쓰이는 것이다. 이러한 동작에는 파일에 기록하거나, 네트워크를 통해서 전송하는 등의 동작이 포함된다.

즉 두 개 이상의 프로그램이 텍스트로 된 데이터를 전송하면서 문자가 깨지는 것을 방지하기 위해서는 양측이 모두 인코드/디코드할 수 있는 공통된 규격이 필요하며, 이것은 꼭 유니코드가 아니더라도 필요한 것이다.예를 들어 한 때 많이 쓰이던 EUC-KR은 한글 문자열을 저장/전송하는데 사용하는 규격이다. 비록 이것이 한국어 외의 문자는 제대로 지원하지 못한다 하더라도 한글을 이해하고 표현할 수 있는 문자 체계에서는 어떤 파일이나 데이터가 EUC-KR로 인코딩되었다는 사실을 알고 있고, 이 인코딩을 디코드할 수 있는 코덱이 있다면 해당 데이터를 읽어서 온전한 한글 문자열로 표현할 수 있을 것이다.

인코딩의 선택

뭔가 텍스트를 작성해서 저장할 때에는 가능하면 유니코드의 인코딩인 UTF-8, UTF-16 등을 사용하도록 하자. 아무래도 가장 널리 쓰이기 때문이다. 예전에 많이 쓰이던 EUC-KR의 경우에는 너무 오래된 표준이고, 사실 확장 완성형 한글을 위한 인코딩도 아니기 때문에 2350자밖에 인코딩할 수 없다. EUC-KR이나 CP949등의 인코딩은 코드페이지가 한국어라는 가정이 들어가므로 안전한 인코딩이 될 수 없다. 그렇기 때문에 한국어를 썼다고 한국어 인코딩을 사용해야하는 것이 절대 아니다.

마찬가지로 일본어를 썼다고 EUC-JP나 그외 다른 일본어 인코딩을 쓰는 것도 그리 추천하지 않는다. 가급적이면 유니코드 계열의 인코딩을 사용하자.

프로그램 소스코드의 인코딩

현대의 많은 프로그램 소스 코드 컴파일러 혹은 해석기들은 유니코드를 지원하며, 소스코드가 UTF-8로 인코딩되어 있을 것이라 가정한다. 애플이 사용하는 Clang이 그렇고, 파이썬도 3.x 부터는 그러하다. (그러니까, 파이썬3를 쓰는 사람들은 utf8로 소스를 저장하는 이상, #-*-coding:utf8을 쓰지 않아도 된다.) 대부분의 프로그래머는 아스키코드 범위 내의 문자를 써서 소스를 작성하기 때문에 이 부분에서 크리티컬한 문제가 생기지는 않는 편인데, 주석이나 소스 내에 하드 코딩된 문자열에서 한글이나 한자, 일본어 같은 문자를 쓴다면 소스 코드를 저장할 때 인코딩에 대해 주의를 기울일 필요가 있고, 가능하면 인코딩을 선택할 수 있거나, 최소한 UTF-8을 사용해서 저장하는 소스코드 편집기를 사용해야 할 것이다.