본문 바로가기

JOB Tips/Windows Programming

Character set, Character encoding, Code set, Code page 그리고 유니코드

완성형과 조합형
완성형은 글자 자체를 하나의 형태로 보고 코드화한 것이고
조합형은 총 한 글자로 표시되는 바이트(보통 2바이트)를 비트로 나누어 초성, 중성, 종성으로 할당해 글자를 표현하는 방식이다.
완성형은 현재 KSX-1001(옛 표준이름 KSC-5601)이라는 표준이 많이 쓰이고 있으며 조합형은 요즘 거의 쓰이지 않는다.
조합형도 여러 가지가 있어 논란이 되다가 결국엔 1987년에 완성형만이 표준으로 되었다.
후에 상용 조합형도 표준으로 들어갔으나 이미 표준이된 완성형만이 널리 쓰이게 되었고 2350자밖에 표현이 안되는 완성형이 윈도우즈에서 쓰이므로 지금까지도 가장 널리 쓰이는 글자 표현 체계가 되었다.


Character set, Character encoding, Code set, Code page
Character set이란 문자의 집합이다. 단, 이때 각 문자에는 숫자 코드가 부여된다. 그렇지만 숫자 코드가 컴퓨터 상에서 어떻게 표현되는 가는 정해지지 않은 상태라고 보면 된다.

Character encoding이란 Character set에 좀 더 제약이 강해서 컴퓨터 상에서 어떻게 표현되는 가까지 정해진 상태의 문자의 집합이다. 같은 그림이라도 압축 방법에 따라 gif, png, bmp 등등의 파일 형식이 있듯이 Character set과 encoding의 차이를 이해할 수도 있을 것이다. 실제 예를 들면 완성형 한글인 KSC-5601 Character set은 UNIX에서는 EUC-KR이란 encoding으로 표현되고 윈도우즈에서는 CP-949란 encoding으로 표현된다. 오래 전에는 Character set과 Character encoding이 같은 말이었다. 그러나 언젠가부터 시스템의 종류도 많아지고 다국어 시스템의 지원 등등 여러 여건들에 의해 Character set에서부터 Character encoding이 분리된 것이다.

Code set이란 말은 어쩔 때는 Character set의 의미로 어쩔 때는 Character encoding의 의미로 사용된다. 그렇다 보니 문맥을 보고서 적당히 해석해서 사용해야 한다.

Code Page는 IBM에서 사용하던 말로 Character encoding과 같은 것으로 보면된다. MICROSOFT에서 DOS를 만들 때 IBM과 같이 만들었기 때문에 MICROSOFT에서는 Code page라는 말을 많이 사용한다.

MBCS, SBCS, DBCS
MBCS이란 Multi Byte Character Set으로 하나의 문자를 표현하는데 코드가 문자에 따라 한 바이트로도
여러 개의 바이트로도 표현되는 encoding을 의미한다. 완성형 한글인 경우 영문은 1바이트로 표현되고 한글/한자는 두 바이트로 표현된다.

SBCS이란 Single Byte Character Set으로 하나의 문자를 표현하는 code가 항상 한 바이트로 표현될 수 있는 encoding을 의미한다. 즉, 우리가 흔히 아는 ASCII라고 생각하면 된다.
DBCS은 Double Byte Character Set으로 하나의 문자를 표현하는 코드가 한 바이트나 두 바이트인 encoding을 의미한다.

윈도우즈 환경에서는 SBCS와 DBCS가 MBCS의 특수한 경우로 처리된다.

KSC-5601, UHC, UNICODE, UTF
프로그램을 하다 보면 문자셋에 대해서 많이 고민을 하게 된다. 이는 KSC-5601(KSX-1001)로 대표되는 완성형 한글은 2350 자가 정의 되어 있으며, UHC(Unified Hangul Codeset)는 KSC-5601과 완벽히 호환이 되며 총 11172개의 한글이 정의되어 있다.
대부분 사용하는 한글 윈도우즈에서는 통합 완성형(UHC)이라고 하는 확장 형태의 완성형 한글('가'->0xB0A1)을 지원한다.
이와는 별도로 ISO에서 전세계의 문자를 UNICODE('가'->0xAC00)라고 불리는 통합적인 체계로 만들었다. 현재 UCS2와 UCS4로 불리우는 2Byte 유니코드와 4Byte 유니코드가 있다.
영어 1문자를 표현는데도 2개의 문자가 필요한 단점이 있어서 이 이상적인 유니코드를 표현하는 방법으로 UTF-8('가'->0xEAB080)이라고 하는 문자가 있다. 그러므로 UCS문자와 UTF-8문자는 1대1로 대응이 가능하다.


완성형과 CP-949, EUC-KR
EUC 계열은 유닉스 계열에서 나온 것으로 윈도우즈보다 더 오랫동안 지역화 및 한글화 문제를 겪어서 빨리 대처를 할 수 있었다. 그래서 EUC-KR하면 한국 코드인데 완성형과 일치한다.
즉, EUC-KR = 완성형이다.
윈도우즈에서는 Code Page 949가 완성형인데 변화를 한번 겪어서
UHC(Unified Hangul Codeset, 통합 완성형)라는 이름으로 불리고 있다. CP-949 = UHC로 보면 된다.

l10n
l10n은 localization(지역화)의 약칭이다. 개발자들이 긴 낱말이 싫어해서 약어로 사용하는 말이다. 소프트웨어가 localization되어 있다는 말은 소프트웨어를 사용하는 사용자를 위해 한 언어에 맞추어 개발이 되어 있다는 것이다. 그래서 이 경우에는 한번에 다중 언어를 사용할 수 없다.
현지화는 어떤 제품이나 서비스를 특정한 언어나, 문화, 그리고 현지의 정서에 맞추는 과정을 말한다. 이상적으로는 한 제품이나 서비스는 현지화가 비교적 쉽게 달성될 수 있도록 개발된다. 예를 들면, 설명서의 경우에는 기술적인 삽화 등을 사용함으로써 그 안에 있는 글자들을 다른 나라의 언어로 쉽게 바꿀 수 있도록 하고 이러한 목적을 위해 어느 정도의 확장성을 감안하고 있다. 이러한 권능을 부여하는 과정을 국제화라고도 부른다. 그러므로 국제화된 제품이나 서비스는 현지화하기가 더욱 쉽다.
관용적인 언어들의 번역 외에도 현지화되고 있는 제품에서는 시간대 조정, 통화, 해당 국가의 휴일, 현지의 색상 감각, 제품이나 서비스의 이름, 성별 역할, 그리고 지정학적 요인 등이 반드시 고려되어야할 요소들의 예이다. 성공적인 서비스나 제품의 현지화는 현지의 문화 속에서 개발된 것처럼 보여지는 것이다.

현지화의 커다란 부분 중 하나인 언어의 번역은 때로 자동 번역으로 쉽게 할 수 있다. 그러나 대체로 많은 추가 작업이 소요된다.

i18n
i18n은 internationalization(국제화)의 약칭이다. software가 internationalization되었다는 말을 들을려면 여러 언어를, 예를 들면, 한국어든 중국어든 동시에 입력해서 사용할 수 있어야 한다. multiligual system이란 말과 i18n system은 동일한 말이다.
국제화는 제품이나 서비스를 특정 지역의 언어나 문화에 맞추는 즉, 현지화라고 불리는 과정을 쉽게 할 수 있도록 계획하거나 이행하는 과정을 말한다. 국제화는 때로 번역 및 현지화 능력 부여 작업이라고도 불리는데 여기에는 다음과 같은 것들이 포함된다.

- 하드웨어 레이블이나, 도움말 페이지, 그리고 온라인 메뉴 등 사용자 인터페이스를 설계할 때 더 많은 수의 글자가 들어갈 때를 대비하여 여유를 둔다.
- 웹에디터나 저작 도구 등과 같은 제품을 개발할 때 국제 문자셋 즉, 유니코드를 지원할 수 있게 한다.
- 인쇄용 그래픽 이미지나 웹사이트를 만들어서 텍스트 레이블을 번역할 때 비용이 많이 들지 않게 한다.
- 전세계적으로 통용될 수 있는 예시를 사용한다.
- 소프트웨어의 경우에는 메시지들이 영어와 같은 단일 바이트 문자 코드에서, 한글과 같은 다중 바이트 문자 코드로 변환될 수 있도록 데이터 공간을 확보한다.

UNICODE
모든 글자 표현 체계를 하나로 통합하겠다는 뜻으로 영문권에서 i18n을 위해서 만든 Character set이다.
unicode 1.0에서는 몇 가지 문제가 있어서 표현이 잘안되는 글자가 있었으며 unicode 2.0에서는 완벽하게 한글이 표현된다(고어 포함).
현재는 unicode 5.0이다.
unicode 자체는 어떤 특정한 바이트 형태를 지정하지 않는다. 따라서 encoding이라는게 필요하다. 그러니까 unicode != UTF-8이다. unicode encoding 중에 하나가 UTF-8은 될 수 있다. 예를 들면, unicode "위"(U+C704)를 UTF-8로 표현하면 EC 9C 84가 된다.
현재 unicode에서 널리쓰이는 encoding은
UTF-8, UTF-16, UCS2, UCS4 등이 있다.
유니코드에서 정의하는 Character set은 UCS2와 UCS4가 있다. 우리가 보통 일반 프로그램을 개발할 때는 UCS2를 기반으로 만들게 된다. UCS4는 산스크리트어나 옛 이집트 고어와 같은 것까지 포함한다. 그러므로 보통 유니코드라고 말할 때는 UCS2를 지칭한다. 

UCS2/UCS4는 Character set이면서 encoding으로도 존재한다. 이 encoding의 특징은 UCS2 경우에는 영문을 포함한 모든 문자가 두 바이트로 표현되고 UCS4 경우에는 네 바이트로 표현된다. 이렇게 고정된 길이의 encoding을 쓰면 장점은 문자열 내의 특정 문자를 index로 쉽게 접근할 수 있다는 것이다. MBCS처럼 문자마다 길이가 다른 경우에는 n번째 문자를 접근하려면 문자열의 처음부터 검색을 해야 한다는 점을 생각한다면 문자열 처리에 이점이 있다.
그러나 UCS2 encoding에 장점만 있는 것은 아니다. 문제는 기존의 ASCII 기반으로 된 모든 소프트웨어와 데이터베이스를 UCS2로 업그레이드해야만 UCS2와 호환된다는 것이다. 그래서 이러한 단점을 보완하기 위한 encoding이 UTF-7, UTF-8아다. 이 encoding들의 특징은 기존 MBCS처럼 한 문자가 1바이트도 여러 바이트도 가질 수 있다.
그래서 현재는 UTF-8이 주도적으로 쓰인다. UTF-8은 흔히 말하는 숫자, 영문자 (ascii 대역)에서는 그대로 1바이트로 쓸 수 있고 나머지는 가변이라서 공간을 아끼면서도 실리를 찾을 수 있다. 즉, ascii 파일은 그냥 UTF-8 encoding이 되어있다고 가정해도 상관없는 것이다.
UTF-8은 ASCII로 표현 가능한 영문자는 1바이트로 표현을 하고 다른 문자들은 2~3바이트로 표현을 한다. UTF-16은 4바이트까지 사용한다.

그래서 실제적으로 프로그램이 유니코드를 지원한다고 하면 내부적으로는 UCS2/UCS4 encoding을 사용하고 파일/데이타베이스 같은 외부 자원에 대해서는 UTF7/UTF8과 같은 encoding을 사용한다. 즉 혼용해서 사용하는 것이다.

UTF-8

유니코드는 미래에 나올 글자들까지도 모두 코드화 하자! 그래서 유니코드는 코드이다.
코딩할 때의 그 코드가 아니라 글자 하나하나에 1, 2, 3, 4, ... 식으로 번호(코드)를 매기는 것이다. 숫자는 끝이 없으므로 미래에 새로운 문자가 생겨도 유니코드에 새로 등록만 시키면 된다(그러므로 유니코드는 2바이트라는 것은 잘못된 것이다).

다시 말하면 유니코드는 개념이자 철학에 불과하며 모든 글자를 포함하는 글자들의 코드 집합니다.

그럼 UTF-8은 무엇인가 하면, 유니코드는 말 그대로 코드 맵핑일 뿐 이를 그대로 소스 코드 상에 구현하기에는 무리가 있다. ASCII 코드와의 호환성도 그렇지만 무작정 두 바이트로 표시한다 하더라도(이것이 UCS-2이다) 메모리 낭비도 심해진다.
그래서 나온 뛰어난 인코딩 방식이 UTF-8이다. UTF-8은 바이트 길이가 문자에 따라 다양해 질 수 있다. 가장 좋은 점은 1바이트일 경우는 ASCII 코드와 동일하다는 것이다. 덕택에 C-like 스트링에서는 문자열의 끝을 표시하기 위해 '\0'을 쓰더라도 문제가 없다. UCS-2 같은 경우는 무조건 두 바이트니 이것조차 '\0\0'으로 표시해야 하는 난감함이 있다.

UTF-8에서 한 바이트로 표시될 수 있는 문자는 ASCII와 호환되므로 당연히 최상위 비트가 0이 된다.
두 바이트로 표현되는 문자면(유니코드 0x80~0x7FF) 첫 바이트는 110xxxxx의 형태의 비트가 된다.
세 바이트로 표현되는 문자면(유니코드 0x800~0xFFFF) 첫 바이트는 1110xxxx의 형태의 비트가 된다.
네 바이트로 표현되는 문자면(유니코드 0x80000~0x10FFFF) 첫 바이트는 11110xxx의 형태의 비트가 된다.
그리고 두 바이트 이상의 문자 중 첫 바이트 외의 문자는 모두 10yyyyyy 형태가 된다.

이 방법으로 알레프()를 UTF-8로 인코딩해 보겠다(알레프는 히브리어 문자의 첫 글자이다).
알레프는 유니코드로 0x5D0, 즉 U+05D0의 코드를 가진다. 5D0은 위의 범위에 따르면 두 바이트로 표현될 수 있다(110xxxxx, 10yyyyyy의 형태).
0x5D0은 이진수로 바꾸면 101 1101 0000의 11비트로 나타낼 수 있다. 5개의 x와 6개의 y에 순서대로 써 주면 된다. 즉 11010111, 1010000이 UTF-8로 인코딩 된 알레프가 되는 것이다.

사실 유니코드를 UTF-8로 표현하는 것은 실제 코딩에서는 크게 활용도가 없을 지도 모른다. UCS-2와 UTF-8을 같이 다루는 경우는 거의 없다.

유니코드 UCS2에서 UTF8로 변환

윈도우즈 API에서 보면 WideCharToMultiByte라는 함수가 있다. 이 함수의 인터페이스는 다음과 같다.
int WideCharToMultiByte(
    UINT CodePage,            // code page
    DWORD dwFlags,            // performance and mapping flags
    LPCWSTR lpWideCharStr,    // wide-character string
    int cchWideChar,          // number of chars in string
    LPSTR lpMultiByteStr,     // buffer for new string
    int cbMultiByte,          // size of buffer
    LPCSTR lpDefaultChar,     // default for unmappable chars
    LPBOOL lpUsedDefaultChar  // set when default char used
);
이 함수의 첫 번째 매개변수 CodePage에 CP_UTF8를 할당하여 사용한다.

아래는 UCS와 UTF-8의 변형 방법이다.
보는 것과 같이 7비트 이하 문자들은 한개의 문자로 표현이 가능하며 유니코드로 0xFFFF까지는 3byte를 이용하여 표현이 가능하다(즉, 한글 1글자(2byte)가 UTF-8로 표현시 3byte가 된다).

RFC 2279 : UTF-8, a transformation format of ISO 10646

   UCS-4 range (hex.)      UTF-8 octet sequence (binary)
   0000 0000-0000 007F     0xxxxxxx
   0000 0080-0000 07FF     110xxxxx 10xxxxxx
   0000 0800-0000 FFFF     1110xxxx 10xxxxxx 10xxxxxx

   0001 0000-001F FFFF     11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
   0020 0000-03FF FFFF     111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
   0400 0000-7FFF FFFF     1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

Windows 9x와 Windows NT 계열 OS의 차이
Windows 9x (Windows 95, 98, me) 계열은 Windows API를 표준적으로 ANSI 버전을 지원한다. 즉, UNICODE를 Windows API에서 직접 지원하지 않고 MBCS만을 직접 지원한다. 이때, ANSI는 MBCS라고 생각해도 무방하다.

Windows NT (Windows NT, 2000, XP)계열은 Windows API를 ANSI 버전과 UNICODE 버전을 모두 지원한다. ANSI버젼의 API를 쓰는 경우에는 OS가 API를 다시 UNICODE 버전으로 변환한다. 그러므로 UNICODE 버전으로 만들어진 프로그램은 windows NT계열에서 약간의 속도 향상을 가져올 수 있다. 그러나 windows 9x에서 동작하지 않는 단점이 있다. 그래서 Microsoft에서는 Windows 9x계열에서 UNICODE 버젼으로 개발된 소프트웨어를 손쉽게 동작하게 하기 위해서 Windows 9x용 UNICODE 지원 dll을 제공한다. 컴파일시 이 dll을 이용하면 UNICODE 버전으로 개발되었어도 Windows 9x계열에서 동작 가능하다.

C/C++의 표준 문자열 정의
"안녕"이라고 literal을 표현하면 이것은 MBCS (언어 규격 표준 용어로는 mcs)이다.
L"안녕"이라고 literal을 표현하면 이것은 UCS encoding (언어 규격 표준 용어로는 wcs)이다.
어떤 encoding을 따르는 것인 가는 Compiler와 OS에 따라 다르다.
예를 들어 Windows에서 Visual Studio 6.0 한글 버전을 사용하는 경우 MBCS로 컴파일시 CP-949를 따르게 된다.
char 데이터 표현은 MBCS의 문자를 표현하는데 사용한다. char는 사실 1바이트로 고정되어 있으므로 MBCS의 문자는 한 char 또는 두 char로 표현된다.
wchar_t 데이터 표현은 UCS의 문자를 표현하는데 사용한다. 많은 경우 2바이트이지만 시스템에 따라 4바이트거나 8바이트일 수도 있다. 이러한 UCS 문자를 언어 규격 표준 용어에서는 wide character라고 한다.

"한글abc"가 유니코드로 써있다면 한글과 영어 구분
영어와 한글은 code range가 다른 영역에 위치하므로 영역 검사로 충분히 구분 가능하다.
자세한 것은 www.unicode.org에 있는 문자 테이블을 참조하시오.
참고로 MBCS로 컴파일된 경우 완성형을 쓰기 때문에 영문 검사시 if (ch < 0x80)해도 무리가 없다.

유니코드와 파일
유니코드 switch로 compile을 처음 해보는 경우 마추치게 되는 문제의 하나가 왜 파일에 저장시 유니코드로 저장되지 않는가 하는 점이다. 앞에서 말했듯이 유니코드라고 하더라도 다양한 encoding이 있으며 유니코드 switch는 API 함수와 자료들을 위한 ucs2 encoding만을 지원한다는 점이다. 즉 파일에 저장한다던지 UTF encoding 등으로의 변환 등은 여전히 전적으로 개발자의 몫으로 남는다는 점이다.
파일로 저장시에는 많은 경우에는 UTF8 encoding을 사용하게되는데 이때는 WideCharToMultiByte API를 사용해서 변환하면 된다.
UTF 계열이 아니라 UCS를 바로 저장하는 경우에는 조심해야 하는 것이 byte order이다. 이것은 CPU에 따라 다르게 되어있는데 intel 계열은 little endian이라 불리우는 바이트 순서로, 그 외 mac이나 workstation 계열은 많은 경우 big endian으로 불리우는 바이트 순서를 가진다. 그러므로 CPU와 OS에 따라 big endian format으로 little endian format으로 저장하는 것에 차이가 있다.


유니코드 용어의 이해
유니코드 관련 문서를 읽다보면 가장 많이 마주치는 용어들이 UCS2, UCS4, UTF8, UTF16, UTF32 등과 같은 단어들이다.

기본언어판, BMP (Basic Mulitilingual Plane)
유니코드의 첫 65,536개의 코드를 의미한다.

언어판, Plane (256x256 즉 65,536 개씩의 코드 묶음)
유니코드에서는 현재 17개의 언어판을 사용할 수 있다. 모두 그룹 00에 포함된다.

언어판 그룹, Group (256개씩의 언어판을 묶어 하나의 그룹)
유니코드의 17개 언어판은 모두 Group 00에 있다.
유니코드는 17개의 언어판에 한정되어 정의된다.
반면 ISO 표준(UCS-4)에서는 모두 128개의 언어판 그룹이 정의될 수 있다.
1 Plane = 65,536 code points
1 Group = 256 planes = 256 x 65,536 = 16,777,216 code points
UCS-4 = 128 groups = 128 x 16,777,216 = 2,147,483,648 code points

인코딩, Encoding (문자 집합을 표현하는 방식)
유니코드는 코드 체계 또는 문자 집합을 명명하는 것이며 이를 표현하기 위해서는 UTF-8, UTF-16, UTF-32 등과 같은 인코딩이 필요하다.

UCS-2: Universal Character Set 2(octets)
좀 더 정확하게는 Universal Multipe-Octet Coded Character Set 2이다.
ISO/IEC 10646의 용어로 BMP의 65,536 코드를 정의하며, 2바이트로 표현된다.
1개의 언어판, BMP만이 이에 해당한다.
UCS-2는 인코딩 방법이 아니며 문자코드 자체이다.
여기서 octet이라는 용어를 사용했는데 이 용어는 ISO 쪽에서 사용하는 용어로, 유니코드 진영에서 사용하는 바이트와 같은 뜻이다

UCS-4: Universal Character Set 4(octets)
ISO/IEC 10646의 용어로 4바이트로 표현된다.
모두 128개의 언어판 그룹, 즉 128 * 256 언어판 = 32,768 언어판을 정의한다.
이는 대략 231 = 2,147,483,648개의 코드에 해당한다.
UCS-4는 인코딩 방법이 아니며 문자코드 자체이다.

UTF-8: UCS Transformation Format, 8-bit form
Unicode 표준의 인코딩 방식 중의 하나이다.
표준에서는 17개 언어판의 문자만을 표현할 수 있으나 기술적으로는 UCS-4 전영역의 문자를 표현할 수 있다.
문자에 따라 1 ~ 4(또는 6) 바이트로 표현된다.

UTF-16: UCS Transformation Format, 16-bit form
유니코드 3.0에서는 16을 16비트로 해석한 것이 아니라, 그룹 00의 16개 언어판이라고 써 놓았군요.
UTF-32의 32가 32비트를 지칭하므로 통일성을 위해 16비트로 이해하시는 게 좋습니다.
16비트로 표현한다는 점에서는 UCS-2와 흡사하지만 대행 문자 영역(Surrogates)을 이용하여 16개의 보충 언어판 코드를 표현할 수 있는 인코딩이다. 대행 문자 영역 2개로 16개의 보충 언어판을 표현할 수 있다. UCS-2에서는 65536개의 코드만을 정의할 수 있으나 UTF-16에서는 1백만여자를 더 표현할 수 있다.

UTF-32: UCS Transformation Format, 32-bit form
32비트 즉 4바이트로 각 문자를 표현한다.
이점에서 UCS-4와 동일하지만 17개의 언어판만을 정의한다는 점에서는 UCS-4의 부분집합으로 간주하면 된다. UCS-4와 동일하나 0x00000000 ~ 0x0010FFFF 범위만을 문자 코드로 간주한다고 이해하면 된다.

출처:
UNICODE사용에 관한 FAQ
컴퓨터속의 한글
KSC-5601에 관한글