꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.
Posted by 비회원
안녕하세요. rihwan (유인환) 입니다. 사실 오늘은 제가 글 쓰는 날은 아니지만, 가볍게 개발에 도움이 될만한 내용을 올려볼까 합니다.

저는 주로 Windows 개발만을 해왔기 때문에 Windows를 기반으로 하여 글을 써보도록 하겠습니다. 하지만 원하신다면 언제든지 이외의 환경으로 바꿀 수 있습니다.

한 프로그램에서 전세계의 글자를 동시에 표현하고 싶을 때. 채팅, 게임의 해외 퍼블리싱등을 생각할 때 Unicode 표현을 쓰게 되면 상당히 이익을 보게 됩니다.

약간의 역사적인 흐름을 보자면...

초기 컴퓨터를 생각해보면 당시에는 주로 영미권에서 컴퓨터를 개발해왔기 때문에 알파벳만 표시하면 되었습니다. 알파벳이 26자인가 그러고 그 외 기호들을 포함해도 모두 합쳐도 256개면 충분하던 시기이죠. 그래서 그때 당시에 1 byte 문자 체계인 ANSI를 사용하기 시작합니다 (1 byte는 8 bits이고, 8 bits는 0 - 255까지 256개를 표현할 수 있습니다). 하지만 이 문자 체계는 중국, 일본, 한국등에서 컴퓨터를 사용하기 시작하면서 1개의 문자를 표현하는데 훨씬 더 많은 byte가 필요하게 됩니다. 한글의 경우는 초성, 중성, 종성을 합쳐서 모두 약 11,000개에 달하는 문자가 존재했으며, 중국과 일본의 경우는 저것보다 더 필요하게 된 것이죠. 우선 다른 나라는 잘 모르겠지만 한글의 경우는 ANSI를 그대로 두면서 2 bytes로 확장된 문자 체계를 표준으로 정하여 사용하기 시작했지요. 방법은 ANSI의 경우 최상위 1 bit가 항상 0 (양수)로 표현했지만, 한글은 최상위 1 bit를 1로 두고 (음수) 2 bytes로 확장해서 한글을 표현하기 시작한 것이죠. 불행히도 당시 2 bytes의 한글 표준은 한글의 초성, 중성, 종성의 조합 형태를 제대로 표현하지 않고, 그냥 마구잡이로 순서를 붙여서 사용했었습니다. 물론 표준이 아닌 조합형 표현이 있기는 했습니다만, 표준이 아니었기 때문에 널리 사용되지 못합니다. 이러한 표현 방법을 MBCS (Multi Byte Character Set인가...)라고 부릅니다. MBCS 하에서는 ANSI와 한글이 동시에 표현되면서 가변길이로 문자가 표현되게 됩니다. 즉 한개의 바이트를 읽었을 때 양수이면 1 byte만 읽으면 되고, 음수이면 그 다음 바이트도 빼내어야지 문자를 한개씩 읽어나갈 수 있게 되는 거죠.

문제점 중 하나는 이러한 음수 표현을 이용한 확장이 한국만이 아니라 중국, 일본 및 해외에서 빈번하게 사용되면서 같은 값이 다른 문자를 표현하게 된 것이죠. 즉 한글로 작성된 문서를 중국으로 가지고 갔을 때 그 문서가 한글이라는 사실을 알지 못하면, 뭔지 전혀 알 수 없게 된 것입니다. 또한 세계의 언어를 하나의 어플리케이션에서 동시에 표현하는 것도 거의 불가능에 가까웠습니다.

그래서 나온 것이 세계의 문자를 표준으로 만들자이고, 그로 인해서 Unicode가 만들어지게 됩니다. 초기에는 2 bytes (0 - 65535까지 65536개)면 충분할 것으로 보고 2 bytes Unicode 표준안이 만들어지게 됩니다. 자랑스럽게도 어떤 분께서 2 bytes 표준안에 한글의 코드값 거의 12,000개에 달하는 영역을 얻어내는데 성공합니다. 즉 전체의 거의 1/5을 한글 코드 값이 차지하고 있다고 할까요... 중국 글자는 한글에 비하면 거의 수십배 많은데도 한글이 저 정도를 차지하고 있습니다. -_-b. 또한 2 bytes Unicode 체계에서 한글은 조합형이 아니지만, 조합형처럼 사용할 수 있게끔 잘 구분되어 있어서 초성, 중성, 종성이 분리가 가능합니다!!!! 이것에 대해 궁금하신 분은 제가 오래~~ 전에 쓴 글인

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=7022&ref=7022

위 링크에 가시면 초성, 중성, 종성 분리에 대한 글을 보실 수 있습니다. 어쨋든 다시 유니코드로 들어와보면 2 bytes 체계를 제외하고도 3, 4 bytes, 가변길이 체계등 여러가지 표준안이 있습니다. 그 중에서 2 bytes 체계는 한글, 중국어 (거의 대부분), 일본, 라틴, 기타등등을 거의 다 포함하고 있어서 자주 사용되고 있지요. 그리고 Visual Studio로 프로젝트를 새롭게 만들게 되면 2 bytes 유니코드 체계로 항상 프로젝트를 만들게 됩니다. 그러므로 3, 4 바이트 체계는 우선 가볍게 무시하고 2 bytes만을 대상으로 이 글을 써봅니다요.

정리하자면 유니코드는 다음과 같은 장점과 단점을 가지게 됩니다.

장점.
1. 전세계 문자를 동시에 표현할 수 있다.
2. 문자 처리하는 속도가 빨라진다.
3. 표준이다.

단점
1. 차지하는 메모리 양이 늘어난다.
2. 유니코드와 ANSI, MBCS와의 호환성에 대해서 고민을 해줘야 한다. 오래된 라이브러리는 오직 MBCS나 ANSI만을 지원하기 때문

따라서 장점이 단점을 넘게 되면 사용해볼만 합니다요!

자 이제부터 사용법에 대해서 다뤄보겠습니다.

유니코드는 일반적으로 아예 다른 문자 값을 표현하는 방법이기 때문에 문자열과 문자를 표현할 때 조금은 다르게 쓰게 됩니다.

지금까지 일반 문자열을 "abc", 일반 문자를 'a'와 같이 표현하고, "a아아아"와 같은 표현에서는 자연스럽게 MBCS로 표현하고 있습니다만, Unicode를 쓰기 위해서는 다르게 써야 합니다.

"abc" -> L"abc"
'a' -> L'a'

위와 같이 앞에 L을 붙여주게 되면 유니코드 표현이라는 것을 컴파일러에 알려주는 것이죠. 그래서 컴파일러는 해당하는 문자나 문자열을 유니코드로 인식하게 됩니다. 유니코드를 위한 데이터 타입은 char가 아닙니다.

wchar_t가 공식적인 유니코드 문자와 문자열을 위한 데이터 타입이며, 일반적으로 2 bytes입니다. 음. 이 부분은 향후에 어떻게 바뀔지는 좀 모르겠습니다만, 현재까지 Visual Studio 2008과 2010에서는 2 bytes입니다.

또한 문자열을 화면에 출력하고 싶다면 기존에 printf 또는 cout를 많이 사용하셨겠지만, 이 부분도 달라져야 합니다. 타입이 다르니까요.

wprintf(L"유니코드 출력!\n");
std::wcout << L"유니코드 출력" << std::endl;

흐음... 물론 저렇게 프로그램을 처음부터 만들어가면 아예 유니코드 기반으로 만들어갈 수 있습니다. 하지만 기존의 많은 프로그램 코드들이 유니코드 기반이 아니기 때문에 호환성이 많은 문제가 됩니다.

가장 단순한 해결책이자 조금은 무식한 해결책은 변환이 필요한 곳에서 변환을 해서 값을 전달하고 또 받아올 때 다시 유니코드로 변환하는 방법입니다. 윈도우즈도 리눅스나 맥도 모두 유니코드와 ANSI, MBCS는 지원할터이니 모두 변환하는 함수는 갖추고 있습니다. 표준 함수도 존재하구요. 따라서 표준 변환 함수를 사용하셔서 필요할 때 바꾸면 됩니다.

윈도우즈 기반 제 코드를 좀 보여드리자면 헤더 파일에는...
const char* ToMbcs(const char* psz);
const char* ToMbcs(const wchar_t* psz);
const wchar_t* ToUnicode(const char* psz);
const wchar_t* ToUnicode(const wchar_t* psz);
const TCHAR* ToTString(const char* psz);
const TCHAR* ToTString(const wchar_t* psz);

소스 코드에는
__declspec(thread) static const int    BUFFER_SIZE = 4096;
__declspec(thread) static char        g_MbcsBuffer[BUFFER_SIZE];
__declspec(thread) static wchar_t    g_UnicodeBuffer[BUFFER_SIZE];

const char* ToMbcs(const char* psz)
{
    return psz;
}

const char* ToMbcs(const wchar_t* psz)
{
    g_MbcsBuffer[0] = '\0';
    WideCharToMultiByte(CP_ACP, 0, psz, -1, g_MbcsBuffer, BUFFER_SIZE, 0, 0);
    return g_MbcsBuffer;
}

const wchar_t* ToUnicode(const char* psz)
{
    g_UnicodeBuffer[0] = L'\0';
    MultiByteToWideChar(CP_ACP, 0, psz, -1, g_UnicodeBuffer, BUFFER_SIZE);
    return g_UnicodeBuffer;
}

const wchar_t* ToUnicode(const wchar_t* psz)
{
    return psz;
}

const TCHAR* ToTString(const char* psz)
{
#ifdef _UNICODE
    return ToUnicode(psz);
#else
    return ToMbcs(psz);
#endif
}

const TCHAR* ToTString(const wchar_t* psz)
{
#ifdef _UNICODE
    return ToUnicode(psz);
#else
    return ToMbcs(psz);
#endif
}

위에서 TCHAR에 대해서는 글 조금 더 읽으시면 나올 예정이며, __declspec(thread)가 뭔가라고 궁금하시면 이것은 Thread Local Storage (TLS)입니다. 이 부분은 또 다른 영역이니 다른 글에서 다뤄보지요.

제가 사용한 함수는 MultiByteToWideChar와 WideCharToMultiByte입니다. 윈도우즈 기반 함수이니 필요하시면 표준 변환 함수로 바꾸어 쓰시면 됩니다.

기존의 코드를 Unicode와 MBCS(ANSI)와 호환시키기!

Windows의 경우는 아래의 헤더 파일을 포함시키는 것이 좋습니다. 왜냐하면 MBCS와 Unicode의 변환을 매우 편리하게 해줄 수 있는 다양한 매크로가 존재하기 때문이죠.
#include <tchar.h>

위 헤더 파일 안을 들어가보면 다양한 define들이 들어가 있습니다. 너무 길어서 보기 힘들기에 제가 간단히 정리해보겠습니다.
#ifdef _UNICODE
#define _tprintf wprintf
#define _sntprintf _snwprintf
#define _ttoi atoi

#define TCHAR wchar_t
#define _T( x ) L##x

#else
#define _tprintf printf
#define _sntprintf _snprintf
#define _ttoi _wtoi

#define TCHAR char
#define _T( x ) x

#endif

즉 만약 _UNICODE라는 매크로가 정의가 되어 있다면 _tprintf는 컴파일 하기 전에 wprintf로 해석되게 됩니다. 만약 정의되어 있지 않으면 일반 printf로 해석되게 되지요. 이러한 정의는 단지 printf에 대해서만이 아니라 문자열 관련 거의 모든 함수를 _snprintf의 경우는 _sntprintf로, atoi는 _ttoi로 등등 매크로로 변환을 정의해놨습니다.

위에서 보는 바와 같이 _tprintf 등을 사용하면 Unicode건 MBCS(ANSI)건 신경쓰지 않고 사용할 수 있다는 장점이 있습니다. 문자열 표현에 대해서도 마찬가지로 아래와 같이 쓰면 신경쓰지 않을 수 있지요.

"A" -> _T("A")
L"A" -> _T("A")

위와 같이 쓰면 _UNICODE 매크로에 따라서 유니코드 문자열이 되었다가 다시 MBCS(ANSI) 문자열로 되었다가 변환되게 됩니다.

TCHAR ch = _T('a');

그럼 _UNICODE 매크로는 어디에 선언되어 있을까요? Visual Studio의 경우는 직접 선언하는 것이 아니라 프로젝트에서 마우스 오른쪽 클릭 후 나오는 Property에 가면



위와 같이 보시면 Unicode를 사용할지 MBCS를 사용할지 아님 ANSI를 사용할지에 대해서 선택할 수 있게 되어 있습니다. 리눅스나 유닉스의 경우는 제가 모르겠지만 가능하다는 사실은 알고 있습니다.

One more thing...

그럼 우리라고 위와 같은 확장을 하지 말란 법이 없습니다. 그래서 저는 아래와 같이 주로 사용하고 있지요.
#ifdef _UNICODE
namespace std
{
    typedef basic_string<wchar_t>            tstring;
    typedef basic_istream<wchar_t>            tistream;
    typedef basic_ostream<wchar_t>            tostream;
    typedef basic_fstream<wchar_t>            tfstream;
    typedef basic_ifstream<wchar_t>            tifstream;
    typedef basic_ofstream<wchar_t>            tofstream;
    typedef basic_stringstream<wchar_t>        tstringstream;
    typedef basic_istringstream<wchar_t>    tistringstream;
    typedef basic_ostringstream<wchar_t>    tostringstream;
}
#else
namespace std
{
    typedef basic_string<char>                tstring;
    typedef basic_istream<char>                tistream;
    typedef basic_ostream<char>                tostream;
    typedef basic_fstream<char>                tfstream;
    typedef basic_ifstream<char>            tifstream;
    typedef basic_ofstream<char>            tofstream;
    typedef basic_stringstream<char>        tstringstream;
    typedef basic_istringstream<char>        tistringstream;
    typedef basic_ostringstream<char>        tostringstream;
}
#endif

std::tstring으로 사용하면 string과 wstring가 매크로에 맞춰서 정의되게 되지요. 위와 같은 것을 제외하고서도 많은 유니코드 관련 중요한 점들이 존재합니다. 그리고 현재 프로젝트에 해외 퍼블리싱이 필요하다라고 생각되신다면 과감히 유니코드에 도전해볼 수 있겠지요.

댓글을 달아 주세요