SBCS, MBCS와 WBCS / 유니코드 프로그래밍이 중요한 이유

이번 포스팅에서는 Charset에 대해서 알아보겠습니다. Charset 부분은 조금 내용이 많으면서도 헷갈리는 부분이 많기 때문에, 잘 숙지하시고, 몸에 베어서 사용하는 것이 좋습니다. 또한 이번 포스팅에서 MBCS와 WBCS를 어떻게 적절히 조합해서 사용할 수 있는지를 알아봅시다.


보통 여러분들이 코딩을하거나 문서를 작성하다보면, "인코딩"이라는 단어를 많이 듣게될 것입니다. 문서 작성에서의 인코딩은 Charset(캐릭터셋)을 의미하는 것이고, 동영상 작업에서는 영상 포맷 등을 의마하는 것이지요.


코딩에서 또한 Charset이 존재합니다. 과거에는 ASCII 코드를 사용하였고, 지금은 강화되고, 편한 유니코드를 사용하고 있지요. 하지만 Linux에 있었을 때는 잘 몰랐었는데, Windows에서 와보면 최근 문자열 인코딩에 맞지 않게 아직까지도 아스키 코드로 코딩하고 있는 경우가 대부분이었습니다. 그 이유에 대해서 알아보고, 왜 유니코드를 더 선호하는지에 대해서 알아보는 시간을 가지도록 하겠습니다.



아스키 코드

아스키 코드라는 것은 정확하게 무엇일까요? 여러분들은 그저 단어만 들으셨을 수도 있습니다. 자세하게 설명을 드려보자면, 아스키 코드는 ANSI라고 하는 미국 규격 협회에서 지정한 표준 코드 체계입니다. 여러분들은 이 코드 체계를 통해서 C 언어 코드를 작성할 때, 문자를 표시하고, 숫자를 표시할 수 있습니다.


What is ASCII ?


아스키 코드는 어느 운영체제에서든지 지원하기 때문에, 여러분들이 C 언어에서도 간단히 접해볼 수 있습니다.


#include <stdio.h>
int main()
{
	char ch= 'a';

	printf("character number: %o \n", ch);
	printf("Character number: %d \n", ch);
	printf("Character number: %x \n", ch);

	return 0;
}


아무 문자 한 개를 적어서 8진수, 10진수, 16진수로 출력해보면, 아스키 코드 문자표에 있는 숫자가 출력되는 것을 확인할 수 있습니다. 아스키 코드는 7비트 인코딩으로 총 2^7 = 128개의 문자를 표현할 수 있습니다. 그 중, 33개를 제외하고, 공백을 포함하여 95개의 출력 가능한 문자들로 이루어져 있습니다. 대표적인 문자셋으로 SBCS가 있습니다. 


제가 여기서 숫자도 표시할 수 있다. 라고 말씀을 드렸는데, 여기서 숫자는 Integer나 long과 같은 정수형 타입의 숫자를 이야기하는 것이 아닙니다. 그들의 숫자는 4 Byte로 표현하지만 아스키 코드에서 숫자를 표현할 경우, 다른 문자와 동일하게 1 Byte를 사용하여 표현한다는 점을 꼭 알아두도록 합시다.



SBCS, MBCS

과거, 처음 문자열이 컴퓨터에 표기되었을 때는 영문과 숫자 뿐이었습니다. 최초 기계어가 숫자, BIOS나 그것을 좀 더 구체화 한 어셈블리어, 모두 영어 아니면 숫자입니다. 좀 더 발전해서 B 언어, C 언어 등 또한 영어와 숫자로 표기되어있습니다. 그 때 당시 컴퓨터는 SBCS를 사용하고 있었습니다.


SBCS는 Single Byte Character Set의 약자로, 단일 문자 집합을 말합니다. 단일 문자 집합이라는 것은 문자를 표현하는 데, 1 Byte를 사용하는 문자 집합을 이야기합니다. 초창기 Unix 운영체제에서도 해당 문자셋을 사용하였으며 이 charset은 ASCII 코드에 속합니다.


MBCS는 Multi Byte Character Set의 약자로, 복수 문자 집합이라는 뜻이지만, 영문자와 숫자를 표현하는 데는 SBCS처럼 1 Byte로 표시하 되, 한글과 특수문자 등은 2 Byte로 표현하는 문자 집합이며 현재 Windows 등의 운영체제에서 이 방법을 채택하고 있습니다. 한글 Windows도 아직까지는 MBCS를 채택합니다. 한글을 표현하기 위해 대표적으로 MBCS에서 사용하는 인코딩 기법에는 EUC-KR, CP949가 있습니다.



유니코드

아스키 코드 다음으로 나타낸 Charset은 바로 유니코드입니다. 여러분들이 알고 있는 유니코드는 한글과 영문자, 숫자를 상관하지 않고 모두 2 Byte로 표현한다는 것만을 알고 계실 것입니다. 유니코드는 아스키 코드보다 더 확장된 문자셋으로, 전 세계 모든 문자를 다룰 수 있다는 특징을 가지고 있습니다. 아스키 코드는 영문자, 숫자 등의 표현 제한, 한글이나 아랍어 등의 2 Byte 이상의 문자 표현 제한 등이 있었지만, 유니코드는 이 모든 문자들의 크기를 통일하면서 전 세계 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준 문자셋입니다.


What is Unicode ?


국내에서 대표적으로 쓰는 유니코드 기반 방식의 문자 인코딩에는 UTF-8이 있습니다. 아마 Linux나 Android에서 개발을 해보신 분들이라면, UTF-8이라는 인코딩에 익숙할 것입니다. 하지만 아직 Windows에서는 이 인코딩 방식을 100% 사용하지는 않습니다.


그 이유 중에 하나는 여러분들이 사용하고 있는 대부분의 프로그램들이 유니코드 시절 프로그램이 아닌, 아스키 코드 시절에 개발된 프로그램이거나 아직까지도 Visual Studio에서 유니코드 기반의 프로그래밍을 하지 않고, 아스키 코드 기반의 프로그래밍을 하고 있다는 점입니다. 그런데, 희안하게도 이제 막 개발되고, 사용하기 시작한 Windows Universal App 마저도, 기본 값을 아스키 코드로 사용하고 있습니다..



WBCS

WBCS는 Wide Byte Character Set의 약자로, 확장형 문자 집합이라는 의미를 가지고 있습니다. 유니코드 기반의 WBCS는 아스키 코드처럼 어떤 문자에 제약을 받지 않고, 전 세계의 모든 문자를 컴퓨터에 표현할 수 있습니다. 그래서 Windows 뿐만이 아니라, Linux, OS X, Android, iOS 등의 스마트폰 운영체제에서도 사용하고 있으며, 대표적인 인코딩 방법으로 UTF-8이 있습니다.



완성형 방식과 조합형 방식

아스키 코드와 유니코드 중 어느 것이 더 좋을까? 여러분들이 이렇게 글로만 쓰기에는 어려움이 있기 때문에, 그림으로 한 번 표현을 해보겠습니다.


출처: http://blog.ohmynews.com/q9447/tag/%EC%99%84%EC%84%B1%ED%98%95%20%EC%BD%94%EB%93%9C 인용. 


조합형 방식은 자음과 모음을 초성, 중성, 종성으로 일일이 모든 것을 구분하여 작성하는 방식으로, 그것들을 하나의 바이트로 인식합니다. 총 3바이트를 인식하게 되는 셈인데, 대표적으로 UTF-8 인코딩이 여기에 해당 되고, 이 방식은 유니코드 방식이기도 합니다. 유니코드 조합형이 이에 해당됩니다.


완성형 방식은 하나의 이미 만들어져 있는 한글 문자를 인식하는 방식입니다. 가령, 한글을 표기할 때, 가장 사용 빈도가 높은 2,360개의 문자들만 코드에 반영하고, 나머지는 사용할 수 없다. 여기에 있는 2,360문자 중에 우리 한국어에 없는 뷁, 아햏햏, 등의 은어들은 사용할 수가 없는 이유가 바로 여기에 있습니다. 실제로 아햏햏 등의 은어는 CP949의 아스키 코드와 UTF-8의 유니코드에서 완성형으로 사용할 수 있습니다.


이런 이야기를 하는 이유는 나중에 윈도우 프로그래밍을 하게 되면, 여러분들이 직접 유니코드로 프로그래밍을 할 것인지, 아스키 코드로 프로그래밍을 할 것인지를 결정하게 되는데, 그것에 관련한 것과 유니코드 프로그래밍이 왜 중요한지에서 다루기 위해 일단 Base로 모든 이야기를 다뤄보도록 할 것입니다.


잡담을 하나 하자면, 여러분들이 사용하는 한글과컴퓨터의 한글 시리즈는 현재, 완성형 코드를 사용하지 않고, 조합형 코드를 사용하지만, 한글 2002가 나오기 이전까지는 완성형 코드를 사용하였고, 삼성에서 개발한 훈민정음 또한, 완성형 코드를 사용하고 있습니다. 이전의 아스키 코드가 아닌 UTF-8의 완성형 유니코드를 사용하고 있습니다.


자 그럼 유니코드와 아스키 코드 중 어떤게 더 좋을까요? 당연히 유니코드가 더 좋겠죠. 왜냐구요? 확장성이 좋고, 모든 글자들을 다 쓸 수 있으니깐요. 삼성의 훈민정음 프로그램에서 훈민정음의 궭들의 글씨를 못쓴다는 것은 큰 치명타였죠...




유니코드 기반의 프로그래밍

윈도우에서 유니코드 기반의 프로그래밍을 하는 방법에 대해 알아봅시다. 그 전에, 우리가 사용하는 일반적인 프로그래밍 방식이랑 어떤 차이가 있고, 위에 내용과 어떤 연관이 있는지에 대해 알아보기 위해, 몇 가지 코드를 작성해보겠습니다


#include <stdio.h>
int main()
{
	char str[] = "대한민국2016";

	printf("Character Length: %d \n", sizeof(str));

	return 0;
}


자 위 코드를 여러분들이 작성해서 출력해보면, 13이 나오게 됩니다. 조금 부연 설명을 해보자면, 저는 str이라고 하는 배열 변수에 "대한민국2016"이라는 글자를 저장하였고, sizeof 함수를 사용하여, 대한민국의 바이트 길이를 계산하여 출력하였습니다.





MBCS에서 한글은 2바이트, 숫자/영문자는 1바이트를 사용합니다. 보시다시피 우리는 일반적으로 아스키 코드 기반의 프로그래밍을 하고 있다는 것을 알 수 있습니다. 한글을 사용할 때도, 완성형을 사용하고 있는 것입니다. 한글 Windows의 경우, EUC-KR보다 더 많은 테이블을 제공하는 CP949를 사용합니다. 따라서, '뷁'이나 '아햏햏' 등의 글자도 출력이 가능하다는 것을 알 수 있습니다.


사실, CP949와 EUC-KR은 거의 같다고 볼 수 있습니다. 다만 이 두 개를 이 포스트에서 따로 구분하는 이유는 그래도, CP949가 더 표현할 수 있는 한글의 갯수가 더 많기 때문입니다. 위에서 제가 인용한 설명과 혼동이 없게끔 하기 위해서 따로 설명하는 것으로 하겠습니다.


그렇다면, Visual Studio에서 유니코드 기반의 프로그래밍을 하려면 어떻게 해야 할까?


Microsoft에서는 기존의 아스키 코드를 유지하면서, 유니코드 프로그래밍을 지원하기 위해, MBCS와 WBCS의 자료형을 구분해서 사용하도록 Visual Studio를 설계하였습니다. 따라서, 여러분들이 유니코드 기반 프로그래밍을 Visual Studio에서 하려면, 유니코드형에 맞는 자료형을 써야 합니다.


#include <stdio.h>
#include <string.h>
int wmain()
{
	wchar_t str[] = L"대한민국2016";;

	wprintf(L"Character Length: %d \n", sizeof(str));

	return 0;
}


유니코드 기반 프로그래밍 소스 코드를 한 번 작성해보았습니다. 프로그램을 실행해보면, 아스키 코드를 사용했을 때와 다른 결과값이 나오는 것을 확인할 수 있습니다.





유니코드는 모든 문자를 2 바이트씩 계산합니다. 심지어 배열에서 끝 문자를 확인하는 NULL 조차도 2 바이트로 인식하기 때문에, 2 * 9 = 18 Byte가 나오게 됩니다. 




유니코드 기반의 자료형과 문법

위에서 봤듯, 여러분들이 일반적으로 사용하는 아스키 코드 기반의 프로그래밍 방식은 사실, Windows에서만 아스키 코드 기반의 프로그래밍 방식이라 합니다. 유니코드 기반의 프로그래밍 방식에서 썼던 wchar_t의 경우도, 사실, Windows에서만 유니코드 기반의 프로그래밍 방식이라 하는 것입니다. FreeBSD의 경우, wchar_t는 유니코드가 아닙니다. locale마다 wchar_t가 유니코드가 될지, 다른 코드가 되는지가 결정되기 때문에, (심지어 Linux에서 wchar_t를 실행하면, 4 바이트가 됩니다) 사실상 이 부분은 일단, 윈도우 프로그래밍을 했을 경우에만 이라고 가정을 하고, 포스트를 읽어주시면 감사하겠습니다.


왜 이렇게 다른지 조금 설명해보자면...


#include <stdio.h>
#include <string.h>
#include <locale.h>
int wmain()
{
	_wsetlocale(0, L"korean");
	wchar_t str[] = L"대한민국2016";

	wprintf(L"Character Length: %d \n", sizeof(str));
	wprintf(L"%s \n", str);

	return 0;
}


사실, 여러분들이 아까 위에서 사용했던 코드는 그저 일부일 뿐이고, 전부를 표현하자면, 위와 같은 코드가 됩니다. 차이가 있다고 한다면, setlocale 부분인데, setlocale 함수의 유니코드형은 _wsetlocale이고, 이 함수를 통해, 여러분들은 locale을 CP949로 설정된 상태이어야합니다. 저 같이 영문 Windows를 사용하는 사람이 한글을 출력하기 위해서 사용하는 방법으로, 매우 귀찮은 짓이기도 합니다.


하지만 Linux에서는 코드를 작성할 때, 기본 locale이 setlocale(0, "ko_KR.utf8")이기 때문에, 자동으로 코드를 작성할 때 유니코드로 작성이 되고, 한글을 출력할 떄도 문제가 생기지 않습니다. 따라서, 여러분들이 wchar_t 등의 유니코드 자료형이나 함수를 사용할 떄, 이 포스트를 참고하는 경우, 반드시 Windows에서만 사용하시기를 권고합니다.




유니코드 기반 프로그래밍이 중요한 이유

Microsoft에서는 기존에 개발된 윈도우 프로그램들이 대부분 아스키 코드를 기반하고 있고, 이들의 호환성을 위해 Windows 운영체제를 100% 유니코드로 전환할 수 없다는 입장을 밝히고 있습니다. 게임 프로그램 중에서도 아스키 코드를 사용하고 있는 프로그램이 꽤 있는 것으로 알고 있습니다. 여러분들이 한 번 쯤 해보신 게임 중에 하나, 바로 스타크래프트1 시리즈도 아스키 코드 기반입니다. 여러분들이 지금 현재 사용하고 있는 Windows 7이나 Windows 10 운영체제에서 스타크래프트의 한글 입력이 원활히 되는 이유는 바로 이 때문입니다.


하지만, 이제 점점 운영체제들이 유니코드를 사용하는 추세로 움직이고 있고, 아직까지 유일하게 Windows 운영체제만 기존 호환성 유지를 위해, 아스키 코드를 사용하고 있을 뿐입니다. 


왜, 유니코드로 갈아탈까요? 사실, 유니코드가 조합형이라서, 더 많은 것을 표현할 수 있지만 프로그램에 사용하면, 아스키 코드는 내가 표현하는 글자대로 바이트 수가 다르기 때문에 프로그램이 가볍고, 유니코드는 모든 문자가 2 Byte ~ 4 Byte이기 때문에, 프로그램이 더 무거워지는 것이 되지 않나? 라는 의문을 가질 수 있다. 물론 이 말은 확실히 프로그램의 크기가 늘어날 수는 있겠지만, 그렇다고 프로그램이 느려지거나 무거워지기 까지는 하지 않는다.


오히려, 이런 표현력이야 말로, 여러분들의 프로그램이 다국어 언어를 지원하는 데, 유리해질 수 있고, 새로이 개발되는 프로그램과 호환성을 유지할 수 있는 좋은 정점이 될 수도 있다. 하지만 아직까지도 많은 개발자들이나 개발자를 꿈꾸는 학생들이 유니코드 프로그래밍의 중요성을 많이 알지 못하다는게 현실이다.


그렇다면, 우리가 한글을 지원하는 프로그램 개발을 위해, 유니코드와 아스키 코드를 따로 개발해야할까? 그러면 시간과 인력 소모가 장난아닐텐데.. 우리가 개발하는 프로그램에 아스키 코드와 유니코드를 동시에 지원하기 위해 쉽게 개발할 수 있는 방법은 없는 것일까?




유니코드와 아스키 코드를 동시에 지원하는 프로그램 개발

사실, 제목이 조금 우습지만, 동시에 지원한다기 보다는 여러분들이 똑같은 자료형을 쓰고, 유니코드와 아스키 코드 기반의 윈도우 프로그램을 개발할 수 있는 방법을 여기에 남기고자 합니다. 이 방법은 Microsoft에서 채택하고 있는 방법이자, MSDN에서 이렇게 권장하고 있고, 우리는 이를 위해 새로운 헤더 파일과 또 다른 새로운 자료형을 배워야 합니다.


#include <tchar.h>


저는 Visual Studio 2015 Update 2 버전을 기준으로, 설명을 드리겠습니다. 이 부분에 대해서는 Visual Studio 버전마다 다를 수 있음을 반드시 참고하시기 바랍니다.


tchar.h 헤더 파일은 여러분들이 아스키 코드와 유니코드의 동시 지원할 수 있는 쉬운 개발을 위해 만들어진 Visual Studio 독자적인 헤더 파일로 오직 Windows에서만 사용할 수 있습니다. 


#include <stdio.h>
#include <locale.h>
#include <tchar.h>
int _tmain()
{
	_tsetlocale(0, _T("korean"));
	_TCHAR str[] = _T("대한민국2016");

	_tprintf(_T("Character Length: %d \n"), sizeof(str));
	_tprintf(_T("%s \n"), str);

	return 0;
}


기존 함수와 다른 점이 있다면, 앞에 "_t"가 붙었다는 것입니다. 또 한 가지가 있다면, 문자열에 각각 _T가 존재합니다. _T는 유니코드일 때, L이 붙고, 아스키 코드일 때는 붙이지 않는 예약어로 tchar.h에서 제공하는 매크로 입니다.


여기서 여러분들이 아스키 코드로 빌드하고 싶다면, 프로젝트의 속성에서 Multi byte Character Set을 설정해주시면 됩니다.




Multi-Byte 설정 이후, 프로그램을 실행한 결과는 다음과 같습니다.



2 * 4 = 8 Byte + 1 * 5 = 5 Byte 하여, 13이 출력되는 것을 확인할 수 있습니다.


반대로 유니코드를 사용할 때도, 프로젝트 속성에서 Chracter Set을 Unicode Character Set으로 설정해주고, 컴파일 하시면 됩니다.




유니코드로 Character Set을 변경해주고, 컴파일하여, 프로그램을 실행해봅시다.




2 * 9 = 18 이 출력되는 것을 확인할 수 있습니다.





끝으로....

여기까지, 유니코드 기반 프로그래밍에 대해서 알아봤습니다. 사실, 이 부분은 제가 프로그래밍하면서 굉장히 싫어하는 부분이기도 합니다. 일단, 설명하기가 굉장히 복잡하고, 이 부분에 대해서 설명하려면 너무나 많은 글을 써내려가야 하는데, 그러기가 쉽지 않습니다. 무엇보다 영문 Windows와 호환성이 떨어져서, 이래저래 설정해야할 것도 많고, 또, 유일하게 Windows만 아직까지 아스키 코드를 고집하고 있기 때문에 다른 프로그래밍 방식을 써야한다는 것도 굉장히 번거롭기도 합니다.


Ps: (2018. 4. 10): 이 글을 포스팅한지가 굉장히 오래되었음에도 불구하고 이 글에 관심을 가져주시는 분들이 많으신 것 같습니다. 오타, 지적에 대한 부분은 언제든지 환영합니다. 이 블로그는 여러분들에게 유익한 글과 저의 개발삽질기를 올리기 위해 운영하고 있는 블로그이며 저 또한 모든 것을 완벽하게 소화해내지 못합니다. 잘못된 것이 있다면 그것을 바로 알면서 공부하게 되는 것이고 저는 그런 기회를 발판삼아 나아가고자 올바른 지적(출처가 명확하고 근거있는 제시) 에 대해서는 가능한한 수용하려 합니다.


여기까지 글 읽어주신 모든 분들 수고 많으셨습니다.



참고: http://blog.ohmynews.com/q9447/tag/%EC%99%84%EC%84%B1%ED%98%95%20%EC%BD%94%EB%93%9C 일부 인용.

comments powered by Disqus

Tistory Comments 0