블로그 이미지
대갈장군

calendar

1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

Notice

2010. 3. 6. 03:53 프로그래밍
왜 언어코드 체계가 이렇게 짜증이 나는지 원... 에휴...

1. SBCS (Single Byte Character Set) : ASCII 문자셋과 ANSI 문자셋 처럼 한바이트로 문자를 표현하는 코드. 고로 최대 256개 까지 가능

문제는 꼴랑 256개 밖에 지원이 안되는 문제점. => 우리나라나 중국은 어쩌라고?
그래서 나온것이 DBCS

2. DBCS (Double Byte Character Set) : 원래 이 DBCS의 의미는 2바이트로 문자를 표현한다는 의미. 하지만 영문과 기호는 8비트로 표현하고 한글은 16비트로 표현하기 때문에 2바이트의 한글과 1바이트의 영문자가 공존 (지 맘대로 섞여 있음). 그래서 다른 말로 MBCS (Multi Byte Character Set) 이라고도 한다.

MBCS의 문제는 글자가 몇개인지 셀때 혹은 바로 앞문자가 뭔지 찾을때 일일이 앞에서 부터 차례대로 검색해야만 알수 있다는 문제점 있음. 왜냐고? 같은 길이를 가지는 문자열이라도 1바이트 영문, 기호랑 2바이트 한글이 몇개씩 들어가 있는지 알수 없으니까.

그래서 등장 한 것이 바로
유니코드다....

3. 유니코드 : 전세계 문자를 표현하는 코드 체계. 16비트로 표현하여 최대 65536개 문자 표현.

이 유니코드는 각 문자가 2바이트씩 딱딱 차지하므로 MBCS에서와 같은 문제는 없다. 하지만 영문자의 경우 공간 낭비가 발생한다.


유니코드의 장점
모든 글자가 같은 크기를 가지므로 글자 수나 검색이 한결 용이함

유니코드의 단점
알파벳은 1바이트만 있어도 충분히 표현가능한데 2바이트를 사용하여 공간 낭비
한글의 고어나 중국의 고어를 넣을 만한 충분한 여유 공간이 없다

고로 위의 유니코드의 문제점 해결이 필요해 졌으니, 그 대안으로 등장한 것이 UTF 다.

4. UTF (UCS Transformation format) : UTF-1, UTF-7, UTF-8, UTF-16, UTF-32등 유니코드 바이트 스트림에 따른 서로 다른 인코딩 방식을 총칭.

UTF는 유니코드를 확장하기 위한 인코딩 방식인데
UTF-16의 경우는, 우선 일반적으로 유니코드 2바이트 형식으로 표현가능한 글자는 그냥 2바이트로 표기하고 만약 한자 고어, 한국 고어 같은 것이 필요하면 4바이트로 확장하여 사용하는 것인데 한마디로 가변 코드 체계다.

이것도 MBCS처럼 2바이트 유니코드와 4바이트 코드가 공존하므로 약간 다루기 까다롭다는 문제가 있구만..

UTF-8의 경우는 또 조금 다른데 이것은 미국이나 유럽등 알파벳을 사용하는 나라를 위해서 사용하는 인코딩 기법이다.

알다시피 알파벳은 1바이트면 충분하다. 그래서 UTF-8의 경우 가변 길이를 가지는 코드체계로써 만약 알파벳이면 1바이트 한글, 중국어면 3, 4바이트를 이용하는 효율적인 방법이다.

고로 만약
문서에 한글(고어 말고)이 많으면 UTF-16이 유리할 것이고 만약 알파벳이 많으면 UTF-8이 유리하다.


헌데 UTF-8이 훨씬도 똑똑한데 왜 그러냐면,
1. 문자 코드 길이 계산이 쉽다. 상위 첫 비트가 중복되는 경우가 없으므로 (고유 태그) 길이 계산이 용이하다. (길이 계산 용이)
2. 가변 길이의 코드도 앞과 뒤가 고유 태그를 가지므로 현재 바이트의 헤더 부분만 보고도 이 바이트가 문자의 앞인지 아니면 중간인지 알수 있다. (글자 검색 용이)
3. 문자열 검색이 용이하다. 왜? 마찬가지로 중복 코드가 없으므로.

추가로 중요한 것 한가지는 윈도우 95랑 98에서는 유니코드 지원하지 않는다 (기본적으로) 물론 업뎃하면 된다고는 하지만 완벽 지원은 안된단다. (지금도 그런지는 모름)

프로그램짜면서 사용해야 하는 실제 상황을 보자꾸나...


#define UNICODE
#include <windows.h>

UNICODE를 windows.h 전에 선언해주면 그 프로젝트는 유니코드로 컴파일 된다.

wchar_t *lpszClass = L"UniTest1";

유니코드로 문자열을 선언하기 위해서는 위 명령처럼 wchar_t 타입을 사용해야 하고 L을 " " 문자열 앞에 붙여야 한다. 

여기서 wchar_t는 사실 unsigned short (16 비트)이고 L은 뒤에 있는 문자열을 2바이트 유니코드로 변환하여 저장하라는 매크로다. 이 L이 없으면 컴파일러는 기본적으로 ANSI (1바이트 문자열 형식)로 저장하려 한다. (에러 뜰거임)

그러면 프로그램짤때 유니코드 쓰냐 안쓰냐에 따라서 선언문이 바뀌어야 하느냐? 만약 YES라면 얼마나 불편하겠는가. 그래서 등장한 놈이 TCHAR인데...

typedef wchar_t WCHAR;
typedef char CHAR;

#ifdef UNICODE
typedef WCHAR TCHAR;
#else
typedef char TCHAR;
#endif

위의 전처리를 통해서 ANSI든 유니코드든간에 TCHAR을 사용하면 UNICODE의 선언 여부에 따라 자동으로 컴파일러가 골라쓰게 된다. 즉, UNICODE가 선언되어 있으면 TCHAR이 WCHAR이고 결국 이건 wchar_t이며 이건 결국 unsigned short이다. 만약 UNICODE 선언이 없다면 TCHAR은 걍 char이다. 

마찬가지로 문자열에 대해서는

typedef WCHAR *PWCHAR, *LPWSTR, *PWSTR;                   // 유니코드 사용시 문자열 포인터 타입
typedef CONST WCHAR *LPCWCH, *LPCWSTR, *PCWSTR;   // 유니코드 사용시 상수형 문자열 포인터 타입
typedef CHAR *PCHAR, *LPSTR, *PSTR;                             // 유니코드 사용 안할때 문자열 포인터 타입
typedef CONST CHAR *LPCCH, *LPCSTR, *PCSTR;             // 유니코드 사용 안할때 상수형 문자열 포인터 타입

#ifdef UNICODE                                                 // 유니코드 사용시는 PTSTR = LPWSTR, LPCTSTR = LPCWSTR
typedef LPWSTR 
PTSTR, LPTSTR;
typedef LPCWSTR 
LPCTSTR;
#else                                                               // 유니코드 사용안할때는 PTSTR = LPSTR, LPCTSTR = LPCSTR
typedef LPSTR 
PTSTR, LPTSTR;
typedef LPCSTR 
LPCTSTR;
#endif

인데 졸라 복잡해 보이지만 간단하다. 문자열 포인터나 상수형 문자열 포인터를 써야 한다면 간단하게 걍 PTSTR, LPTSTR, LPCTSTR등을 쓰면 된다. 만약 내가 #define UNICODE를 시작전에 선언했다면 프로그램은 알아서 WCHAR 을 사용할 것이고 만약 유니코드 매크로를 선언하지 않았다면 알아서 CHAR을 사용한다. 위의 선언에서 잘 보면 W가 있고 없고의 차이인데 이것이로 WCHAR이냐 아니면 CHAR이냐의 차이다.

고로 결론적으로 유니코드도 되고 ANSI도 되는 코드를 짜려면 문자 선언시는 TCHAR, 문자열 선언시는 LPTSTR을 써라는 말이다.

문자열 상수에 대한 매크로가 존재하는데 바로 TEXT("     ") 이다. 이놈을 사용하면 마찬가지로 매크로에서 정의된 방법대로 유니코드냐 아니냐에 따라서 알아서 저장해 준다. 만약 UNICODE 매크로가 선언되어 있다면 TEXT()는 알아서 문자열 앞에 L을 붙여주고 (이 L이 유니코드로 문자열을 변경하라는 매크로) 만약 UNICODE 매크로가 선언 되지 않았다면 걍 ANSI 문자열로 문자열을 생성한다. 고로 사용자는 단순하게 #define UNICODE를 프로그램 시작부분에 쓰느냐 안쓰느냐에 따라서 간단하게 유니코드와 ANSI 코드 사이를 왔다 갔다리 할 수 있는 거다

사실 알고보면 윈도우가 제공하는 각종 API 함수들중 문자열을 받거나 리턴하는 대부분 UNICODE 타입과 ANSI 타입을 지원하는 두 종류를 가진다. 예를 들어 TextOut 함수를 보면 실제로는 TextOutW와 TextOutA라는 두 함수로 구성되어 있고 UNICODE의 선언 여부에 따라 적절한 함수를 호출한다. 여기서 W는 Wide라는 의미로 Unicode를  의미하고 A는 ANSI를 의미한다.


프로그램을 작성할 때는 윈도우가 제공하는 API 뿐만 아니라 오래된 C 함수 및 C++함수들을 사용해야 하는데 대표적인 것이 문자열의 길이를 구하는 함수인 strlen()이다. 대략 10년 전쯤 처음 컴퓨터 프로그래밍 수업시간에 들었던 기억이 난다.. ㅡ.ㅡ;

아무튼, 이 strlen() 함수는 ANSI 코드를 위한 함수이지 유니코드를 위한 함수가 아니다. 유니코드를 위한 문자열 길이 구하는 함수는 wcslen() 이라는 놈이다. 하지만 걱정할 필요가 없는것이 위에서 해왔던 것 처럼 이 둘 사이를 쉽게 왔다 갔다 할 수 있는 일반형 함수가 존재한다. 

tchar.h에 보면 UNICODE의 사용여부에 따라 알아서 strlen() 또는 wcslen() 함수를 사용하도록 해주는 일반형 함수가 정의되어 있는데 대표적인 예로 _tcslen, _tcscpy, _tcscmp, _tcsicmp 등이다.

#ifdef _UNICODE
typedef _tcslen wcslen;
#else
typedef _tcslen strlen;
#endif

위 매크로 선언에 의해서 알아서 wcslen과 strlen 함수를 _UNICODE 매크로 선언 여부에 따라 적절히 선택한다.

참고로 코드를 보면 _UNICODE와 UNICODE 두가지가 공존하는데 이것은 같은 의미의 매크로로써 하나는 컴파일러가 언어차원에서 정의한 것이고 나머지 하나는 운영체제가 정의하는 거란다. 젠장할...

아무튼 결론적으로보면 사용자는 프로그램 내부에 TCHAR, _tcslen, TEXT(" ") 와 같은 UNICODE 선언 여부에 따라 적절한 함수를 선택하는 매크로 함수들을 사용하면 손쉽게 UNICODE 용으로 컴파일 할수도 있고 혹은 ANSI 용으로 컴파일 할 수도 있다는 점이다. 


Visual Studio 의 프로젝터 Properties 를 클릭해 보면 General 속성에서 Character Set에 유니코드와 MBCS 둘 중 하나를 선택할 수 있다. 여기서 유니코드를 선택하면 Visual Studio가 알아서 #define UNICODE를 코드에 삽입한다. (얼마나 편한가..)


만약 프로그램을 짤 때 일반화 함수들을 안쓰고 char을 써놓고 UNICODE로 컴파일 하라는 명령을 주게 되면 위 그림처럼 무수히 많은 에러들을 보게 될 것이다. 이것이 일반적으로 가장 빈번히 발생하는 유니코드 관련 에러이다. 문제는 다음과 같은 경우다. 

내가 프로그램을 만드는데 어느 유명 사이트에서 대단한 프로그래머가 만든 아주 유용한 소스 코드를 받았다. 헌데 그 프로그래머는 너무나도 유니코드를 사랑하셔서 모든 함수를 일반화 함수가 아닌 유니코드 함수로 직접 불러다 사용했다. 그런데 내가 만든 프로그램에서는 죄다 MBCS를 사용한다. (char, char * 같은 형태) 

둘을 하나의 프로젝트에 묶어서 컴파일 하려했다... 에러가 200개 뜬다..... ㅡ.ㅡ; 헐... 

이런 경험을 해본 사람이 많으리라 믿는다. 아무튼, 말하고자 하는 바는 프로젝트를 만들때 유니코드와 MBCS가 동시에 존재할 수 없다는 것이다. 그래서 일반화 함수를 사용하는 것이 현명한 방법이다.

TCHAR
TCHAR과 PTSTR 그리고 TEXT() 매크로 함수를 범용성을 위해서 꼭 사용해라... 이말이다.

posted by 대갈장군