jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid 캐나다로 날아가서 실력만으로 수석 그래픽 프로그래머가 된 꽃미남 프로그래머 "Pope Kim" 님의 북미취업 가이드북이 전자책으로 출간되었습니다. 블랙썬  for Kakao 클래식한 던젼 RPG와 간편한 모바일 게임이 만났다! 당신을 검과 마법의 장대한 서사시로 초대하는 [블랙썬 for Kakao]!! 3ds Max를 사용해서 게임용 3D 캐릭터를 셋업하는 방법
이를 위해 오랜 실무를 경험해 온 저자의 고급 노하우들이 공개
위 내용은 GameDevForever의 저자분들의 홍보를 위하여 운영진 자체적으로 올린 광고이며 일체의 수익이 없습니다.(밥좀사줘요~)
Posted by rihwan
안녕하세요. 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가 매크로에 맞춰서 정의되게 되지요. 위와 같은 것을 제외하고서도 많은 유니코드 관련 중요한 점들이 존재합니다. 그리고 현재 프로젝트에 해외 퍼블리싱이 필요하다라고 생각되신다면 과감히 유니코드에 도전해볼 수 있겠지요.

댓글을 달아 주세요

  1. 디치리 2012/01/06 10:25  댓글주소  수정/삭제  댓글쓰기

    좋은 내용 감사합니다.

  2. Favicon of http://kgun86.tistory.com 끼로 2012/01/06 12:28  댓글주소  수정/삭제  댓글쓰기

    저도 유니코드를 사용하지 않는 상태에서 로컬라이징을 한번 경험해보고 그 이후로 시작하는 프로젝트들은 전부 유니코드 기반으로 작업하고 있습니다.. ㅠㅠ 아 그리고 태그는 제가 수정했습니다 "rihwan unicode 유니코드" 이렇게 묶여있었는데 필자프로필이나 연재스케쥴에서 이름 클릭시 태그로 글을 찾게 되어있어서 "rihwan"이 따로 있어야 글이 검색이 되거든요..

  3. Favicon of http://thelevelcat.tistory.com Silverchime 2012/01/06 13:58  댓글주소  수정/삭제  댓글쓰기

    와... 좋은내용 감사합니다 유니코드에 대해 자세히 알게 되었네요!

  4. Favicon of http://https://twitter.com/#!/dozingLamb/following/tweets 잠자는양 2012/01/06 14:49  댓글주소  수정/삭제  댓글쓰기

    로컬 라이징을 유니코드가 아니라 UTF-8으로 사용하자는 글입니다.

    참고 사항으로 좋을거 같아서 링크 겁니다.
    http://altdevblogaday.com/2011/06/10/strings-redux/

    • Favicon of http://kgun86.tistory.com 끼로 2012/01/06 14:58  댓글주소  수정/삭제

      UTF-8을 쓰자는것에 어느정도 동의합니다. 유니코드가 사실 윈도우즈에서는 2바이트를 사용하는데 2바이트로 표현할 수 있는 글자가 세상 모든 글자는 아니니까요.. 그런데 UTF-8에서 한글은 3바이트 ㅠㅠ 근데 altdevblogaday는 영어여서 읽을수가 없.. Orz 이거 무슨말인가요.........

  5. Favicon of http://www.gamedevforever.com 김포프 2012/01/09 08:34  댓글주소  수정/삭제  댓글쓰기

    사실... 북미쪽에서는 아직도 유니코드 무시하는 분위기가 강해요.. ㅎㅎ.. 그냥 여전히 Ascii로 만들어놓고 남들에게 로칼라이징 한다는 분위기가 강함.. -_-; 언어마다 바이너리를 새로 만드는건가... -_-

    • Favicon of http://zinzza.tistory.com zinzza 2012/01/09 13:43  댓글주소  수정/삭제

      북미 외에는 많이 안팔려서겠죠. 블리자드처럼 한국 돈맛을 좀 보면 알아서 음성한글화까지 해줘요--;

    • Favicon of http://www.gamedevforever.com 김포프 2012/01/09 14:53  댓글주소  수정/삭제

      스페이스마린도 한 8개국어인가로 localization했어요.. 근데 유니코드 사용안하고 그냥.. 각 언어별로 새로 바이너리를 찍었음...

      아스키 코드만 있어도 한나라 언어 표현하는데는 문제가 거의 없거든요 -_-

    • Favicon of http://rihwan.tistory.com rihwan 2012/01/09 15:30  댓글주소  수정/삭제

      음 포프님. 그럼 캐나다 같은 경우는 동시에 영어와 프랑스어를 표현해야 할텐데... 어떻게 보통 처리하는지 알려주실 수 있나요? 음 ASCII로는 두 언어를 동시에 표현하기 쉽지 않을거 같은데요...

    • Favicon of http://kgun86.tistory.com 끼로 2012/01/09 16:04  댓글주소  수정/삭제

      메시지를 보낼때 코드페이지도 같이 보내면 가능할것 같긴 합니다만.. 전 유니코드나 UTF-8을 쓸래요.. 한국이 온라인게임을 많이 만들다보니 유독 여러 언어 표현에 신경을 많이 써서 유니코드를 많이 쓰는거일수도 있겠네요.. 사실 한국어 코드페이지로 문자 표현을 해도 한국에서 많이 쓰는 한자나 일본어 표현이 어느정도는 가능하니까요..

  6. Favicon of http://kindtis.tistory.com 친절한티스 2012/01/09 09:51  댓글주소  수정/삭제  댓글쓰기

    전 이렇게 썼었는데...
    typedef basic_string<TCHAR> tstring;

  7. sgpro 2012/01/10 11:00  댓글주소  수정/삭제  댓글쓰기

    고맙습니다. ^^

  8. knight26h 2012/01/10 11:47  댓글주소  수정/삭제  댓글쓰기

    좋은 내용 감사합니다.

    개인적으로는 로컬라이징 작업을 자주 해서 그런지.. 2byte의 유니코드를 사용하여 조금 크기가 늘어나더라도 유니코드가 훨씬 좋은 것 같습니다. 멀티바이트로 필요시에 변환하는게 조금 부담스럽긴 하지만, 요즘은 라이브러리들도 유니코드를 많이 지원해줘서 큰 부담은 못느꼈던것 같습니다.

    여러 국가언어가 동시 표현이 가능하다는 점도 장점인 것 같습니다.
    서비스 관련 작업 할 때 타국가용으로 제작된 클라이언트로도 한글을 입력하면 해당 국가언어와 한글이 동시에 표현이 가능하다는 점이 좋았던 것 같습니다.

    아시다시피 사실 유니코드라고 모든 언어의 한글자가 WCHAR 한개 (2 byte)로 표현되지는 않습니다.
    태국어와 같은 일부 언어들은 유니코드를 사용하여도 한글의 조합형처럼 구성되어 여러개의 WCHAR의 조합으로 구성됩니다. 현재는 이런 부분이 가능하다면 통합되었으면 합니다. 유니코드라고 1글자 = 1 WCHAR 믿고 있었는데 다르니 오히려 큰 단점처럼 느껴지더라구요.

    그리고 TCHAR이나 MFC의 CString과 같이 유니코드도 되었다가, 멀티바이트도 되는 문자형 같은 경우에는, 오히려 혼란을 주거나, 사용은 하였는데 양쪽 모두 고려되지 않은 코드들 때문에 곤란했던적이 많았던것 같습니다. 그래서 오히려 명시적으로 한쪽(유니코드)에 맞추는 것을 선호하게 되더군요.


    • Favicon of http://rihwan.tistory.com rihwan 2012/01/10 18:27  댓글주소  수정/삭제

      음 TCHAR에 대한 부분은 상황에 따라 장점과 단점이 존재하는군요. char -> wchar_t로의 호환 작업을 하다가 보니 TCHAR를 이용한 변환이 훨씬 쉽게 되어서 자주 사용하게 되었지만, 처음 작업을 하는 경우에는 조금 다를 수 있을 것 같네요.

  9. Favicon of http://www.ahappydeal.com/product-67365.html novo 7 elf 2012/07/23 16:53  댓글주소  수정/삭제  댓글쓰기

    너무 소중한 정보 들이라 공식추천님 블로그의 글들을 시간나는데로 처음부터 샅샅이 정독해야겟네요..

  10. 파워짐기 2013/11/05 03:25  댓글주소  수정/삭제  댓글쓰기

    UTF-8 쓰는게 어지간하면 좋더라구요.

    저 인코딩에서는 한글이 보통 3바이트라는 점이 좀 불만이긴 했는데, 그건 '한 글자'일때 이야기고 일반적인 텍스트에는 이렇게 띄어쓰기도 들어가고 문장 부호도 들어가지 않습니까?
    이런 애들은 UTF-8에서 1바이트기 때문에 적당히 상쇄가 되더군요.
    그래서 실제로 똑같은 문장을 UTF-8, UTF-16으로 인코딩 해보면 생각보다 용량 차이가 나지 않습니다. (UTF-8이 그래도 더 크긴 합니다)

    그리고 UTF-8은 char로 처리할 수 있다는게 가장 좋은 점 같네요.
    앞서 wchar_t가 유니코드를 위한 타입이라고 하셨는데 조금 다릅니다. 항상 sizeof(char) == 1 이므로 1바이트가 아닌 char를 위한 char 타입(?)이라고 하는게 정확할 것 같네요.
    sizeof(wchar_t)는 윈도에선 2지만 리눅스에서는 보통 4기 때문에... 이식성도 없습니다.

    어차피, 윈도에서도 유니코드 API만 쓰도록 되어 있으므로 TCHAR 같은건 혼란만 가중시키는듯 하네요. (MS에서도 쓰지 말라고 합니다)
    윈도가 UTF-16 쓰는게 참 걸리긴 하는데... 그래도 UTF-8 쓰고 API 건드릴 때마다 매번 변환시켜주는게 나은 것 같습니다. 인코딩을 한쪽으로 통일하는게 코드가 깔끔해져요.



티스토리 툴바