인코딩과 문자집합: C/C++

이전에 다뤘던 내용들에 이어서, 이번엔 C/C++에서 이런 것들을 어떻게 다루고 있는지 얘기해보려한다. 모든 문자집합/인코딩 들을 다루려면 길어질테니 크게 세 가지만 가지고 얘기해보겠다.

  1. euc-kr을 사용한 문자열 (혹은 다른 DBCS를 쓰는 경우들)
  2. Unicode를 사용하고 UTF-8 인코딩을 사용한 경우
  3. Unicode BMP를 사용하고 UCS2를 사용한 경우

1, 2는 데이터를 저장하기 위해 보통 char를 사용하고, 3은 wchar_t를 쓴다. (이 이하는 그렇게 가정하고 진행)

문자열을 다룬다는 행동은 어떤 것들일까? 일단 데이터 교환을 위해서 서로 알고 있어야하는 것과 일반적인 연산은 다음과 같다고 생각한다.

  • 차지하고 있는 메모리 영역의 크기 or 차지하고 있는 기반 데이터 타입의 수
  • 글자 수 (메모리 영역의 크기와 다름)
  • n 번째 글자를 어떻게 인덱싱할 것인가

일단 1, 2번의 경우 메모리 영역의 크기를 얻는 것은 간단하다. 그냥 strlen() 해버리면 된다 – NULL문자가 절대로 \0 로 사용된 경우를 빼고는 나타나지 않는게 보장된다. 3번의 경우엔 조금 다른 버전의 strlen이 준비되어 있다. wcslen()이란 함수인데, wchar_t 기반의 문자열 길이를 구해준다. 여기에 sizeof(wchar_t)를 곱해주면 메모리 영역의 크기가 된다1.

문자열 길이(=글자 수)를 구하는 연산은 1, 2의 경우 조금 복잡해진다. euc-kr인 경우 어떤 문자도 2 bytes안에 들어가기 때문에, MSB만 읽고 1 byte짜리 글자인지 2bytes 짜리 글자인지 판단하면서 길이를 구할 수 있다. 혹은 (현재 로케일이 euc-kr인 경우에 한해) mbstowcs() 함수의 반환값을 이용하는 방법도 있다2. 좀 더 복잡한 것은 UTF-8의 경우인데, 각 글자의 첫 바이트를 보고 몇 bytes인지 추정할 수 있다. bit 패턴이 0으로 시작하면 1byte, 110로 시작하면 2 bytes, 1110으로 시작하면 3bytes, 11110으로 시작하면 4bytes로 되어있으므로 이를 기반으로 적절히 스킵하면서 글자 수를 셀 수 있다.(물론 조건문이 들어가기 때문에 조금 느릴 수 있다)3

n번째 글자를 어떻게 인덱싱할 것인가? 하는 것은 약간 복잡한 문제다. 고정길이 인코딩인 경우는 매우 쉽다. 그래서 UCS2의 경우 그냥 n번째 wchar_t 데이터를 읽어버리면 그만이다. 반면에 가변길이 인코딩인 euc-kr(mbcs)이나 UTF-8으로 인코딩된 유니코드의 경우 세는 수 밖에 없다.

언급은 안했지만 일반적으로 각 운영체제 혹은 기반하는 컴파일러의 라이브러리에서 이런 MBCS 류의 문자열을 다루기 위한 함수들이 존재한다. Windows 운영체제의 VC++의 경우 _mbs로 시작하는 일련의 함수들이 있고, linux의 경우 **wchar_t의 크기가 32bit**라서 mbstowcs()를 이용해서 변환한 후 고정길이 인코딩처럼 wcs함수들을 사용하면 된다(…그렇지만 라이브러리 레벨에서 각종 함수들이 제공되어 MBCS를 바로 다룰 수 있는 경우가 대부분이다).

사실 이런 연산들의 _비용_을 생각해보면 UCS2가 효율적이지 않나라는 생각을 할 것이다. 당연히 효율적일 수 밖에 없다 유니코드의 모든 글자를 표현할 수 있는게 아니니(…). 모든 글자를 표현하면서도 2bytes 데이터 기반인 UTF-16의 경우에도 똑같은 문제가 있다. 이 이외에도 문자열에 관련된 연산으로 “비교"연산이 있는데, 같은지 여부를 판정하는 것 이외에는 언제나 locale의 영향을 받을 수 있기 때문에 이 글에서는 다루지 않는다

다음 글에서는 Unicode의 특정 인코딩이나 legacy 문자집합의 mbcs로 표현된 문자열들을 “어떻게 서로 바꿀 것인가"를 다뤄보려고 한다.


  1. NULL 문자를 포함하지 않은 메모리 영역의 크기를 말하고 있다. 이 부분을 알아서(…). 그리고 지금 euc-kr을 mbcs의 예로 쓰고 있는 것은 euc-kr이 _매우 단순한 mbcs_이기 때문이다. shift-jis처럼 문자열을 해석하기 위해 내부적인 state를 유지해야하는 것도 있는데 이런 것들은 다루기가 훨씬 복잡하다. ↩︎

  2. mbstowcs()는 현재 로케일의 MBCS; multi-byte character sequence 문자열을 UTF-16으로 전환해주는 함수다. C99에 추가되었음 (여기서 다루는 많은 함수들이 거의 그런 상태). 혹은 mbsrtowcs()를 사용해도됨(MT문제) ↩︎

  3. 10으로 시작하는 패턴이 없는데에는 다 이유가 있다. 중간중간에 끼어있는 바이트 들 – 개별 글자를 표현하기 시작하는 바이트를 제외한 바이트 들 – 은 모두 10으로 시작한다. 이런 특성 때문에 UTF-8으로 인코딩 된 경우엔 strcpy, strlen 같은 함수들을 변경없이 사용할 수 있다. ↩︎