Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
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 31
Tags
more
Archives
Today
Total
관리 메뉴

Study

윈도우즈 시스템 프로그래밍_02_아스키코드 vs 유니코드 본문

Programming/System Programming

윈도우즈 시스템 프로그래밍_02_아스키코드 vs 유니코드

마늘부추 2019. 3. 4. 00:21

02. 아스키코드 vs 유니코드

 

02.1 Windows에서의 유니코드(UNICODE)

 

02.1.1 문자셋(Charactre Sets)의 종류와 특성

 

◎ 종류

 

[아스키코드(ASCII CODE)]
- 미국에서 정의하고 있는 표준.
- 1byte로 문자 표현.
- 알파벳 26자와 그림문자, 다양한 기호들 포함 256가지 문자 표현 가능.

 

[유니코드(UNICODE)]
- 영어권이 아닌 국가에서 사용하는 문자 표현.
- 2byte로 문자 표현.
- 영어, 기호, 한글 포함 65,536가지 문자 표현 가능.

 

◎ 문자셋 표현 방식

 

[SBSC(Single Byte Character Set)]
- 문자를 표현하는데 1byte만을 사용하는 방식.
- 아스키코드가 대표적인 SBCS.

 

[MBCS(Multi Byte Character Set)]
- 문자를 표현하는 데 있어서 다양한 바이트 수를 사용하는 방식.
- MBCS는 SBSC를 포함.
- 아스키에서 정의하지 않는 문자는 2byte로 처리하고 아스키에서 정의하는 문자는 1byte로 처리.

 

[WBCS(Wide Byte Character Set)]
- 유니코드가 WBCS 방식에 해당.
- 모든 문자를 2byte로 처리하는 문자셋.

 

 

 

02.1.2 MBCS 기반의 문자열

 

[예제 2-1]

 

 

배열의 크기 : 10
"ABCDE"(5byte) + "한글"(4byte) + "\0"(1byte) = 10byte

문자열 길이 : 9
"ABCDE"(5) + "한글"(4) = 9


"한글"은 두 글자이지만 단어의 길이가 4로 인식되고 있다. 이것이 바로 MBCS의 문제점이다.

이러한 MBCS의 문제점을 해결하는 것이 모든 문자를 2byte로 처리하는 WBCS 방식이다.

 

 

 

02.1.3 WBCS 기반의 프로그래밍

 

[char을 대신하는 wchar_t]
- char형 변수는 1byte 메모리 공간만 할당되지만 wchar_t형 변수는 2byte 메모리 공간이 할당된다.
- wchar_t의 형태
    typedef unsigned short wchar_t;

 

["ABC"를 대신하는 L"ABC"]
- 에러가 발생하는 경우는 다음과 같다.
    wchar_t str[] = "ABC";
배열 str은 UNICODE 기반의 문자열을 담는 변수로 선언되었음에도 대입되는 문자열은 여전히 MBCS이기 때문이다.
그러므로 이 문장을 다음과 같이 변경해야 한다.
    wchar_t str[] = L"ABC";


문자열 앞에 선언된 문자 L은 "이어서 등장하는 문자열을 유니코드 기반으로 표현하라"라는 의미를 지닌다.
따라서 이 경우에 문자열 "ABC"는 한 글자당 2byte, NULL 문자(역시 2byte)를 포함하여 총 8byte로 표현된다.

 

[strlen을 대신하는 wcslen]

 

[예제 2-2]

 

MBCS1.cpp의 코드와 다른 점은 11번째 줄 int len = wcslen(str); 이다.
strlen 함수는 SBCS 기반의 문자열을 처리하기 위한 함수이므로 유니코드 기반의 문자열을 처리할 땐 똑같이 유니코드 기반인 wcslen 함수를 사용해야 한다.

 

※ 참고!

sizeof는 함수가 아니라 연산자이다.
배열에 어떠한 문자열이 저장되어 있건 상관없이 배열의 크기를 정확히 계산해서 반환해주기 때문에 문자셋에 영향을 받지 않는다.

 

 

[표 2-1] 문자열 조작 함수

 

 

왼쪽에 소개되는 함수들은 SBCS와 MBCS 모두 혼용되는 함수들이다.
그래서 기본적으로 문자열들을 MBCS로 처리하는 Windows에서도 사용이 가능한 것이다.

 

※ 참고!

표 2-1에 등장하는 size_t는 다음과 같이 정의되어 있다.
    typedef unsigned int size_t;

 

여기서 좀 더 완전한 유니코드 기반으로 코드를 짜기 위해 고려할 첫 번째 사항은 다음과 같다.
Windows 2000 이상의 운영체제는 기본적으로 유니코드를 지원하고, 내부적으로 모든 문자열을 유니코드 기반으로 처리한다.
다음 함수 호출 문장을 보자.
    printf("Hello World!");
여기서 전달되는 문자열 "Hello World!"는 아스키에 존재하는 문자열들로 구성되어 있으므로 각 1byte로 처리되고, 이렇게 처리된 문자열이 printf 함수로 전달된다.

문제는 printf 함수는 SBCS 기반 문자열 처리 함수라는 것이다. win2000 이상의 운영체제는 모든 문자열을 유니코드 형식으로 변환하여 처리하기 때문에 위와 같은 코드는 프로그램 성능에 영향을 미칠 수 있다.

 

따라서 이 또한 다음과 같은 유니코드 기반으로 작성하면 성능에 영향을 미치지 않을 것이다.
    wprintf(L"Hello World!");

 

[표 2-2] 문자열 입출력 함수

 

[예제 2-3]

 

 

WBCS1.cpp을 유니코드에 맞게 수정한 코드이다.
"한글"(2) + "ABC"(3) = 5
한글도 2글자로 인식한다.

 

※ 여기서 잠깐!

6행의 #include <locale.h>
10행의 _wsetlocale(LC_ALL, L"korean");

 

wprintf, fputws와 같은 함수들을 통해 유니코드 기반으로 한글을 출력하고 싶을 때, 프로그램이 실행되는 나라 및 지역에 대한 정보를 설정해야 한다. 위 6, 10행은 그를 위해 사용하는 함수이다.
이를 추가했기 때문에 2-3코드의 wprintf 함수에 한글을 사용해도 에러가 나지 않는 것이다.

 

완전한 유니코드 기반으로 코드를 짜기 위한 두 번째 사항은 다음과 같다.

 

[예제 2-4]

 

10번째 행에 밑줄이 나타나 있다.
유니코드 기반의 문자열을 출력하기 위해 fputws 함수를 사용하였는데, 정작 출력하는 문자열이 유니코드 기반의 문자열이 아니기 때문에 나타나는 오류이다.
6번째 줄 main 함수의 두 번째 매개변수 선언을 보면 char형의 문자열 포인터가 선언되어 있다. 이는 SBCS 기반으로 문자열을 전달받는다는 의미이므로
    wchar_t* argv[]
로 수정해 주어야 한다.
그러나 이 부분만 수정해서는 원하는 결과가 나타나지 않는다. 한가지 더 수정해 주어야 하는데 그는 바로 main이다. 함수 이름이 main이라 하면 이는 MBCS 기반으로 문자열이 구성된다는 뜻이다.
그렇기 때문에 WBCS 기반인 wmain으로 수정해주어야 문자열이 유니코드 기반으로 구성된다.

 

예제 2-4를 적절히 위의 설명에 따라 적절히 변경한 후 실행하면 정상적인 출력값이 나온다.

 

 

 

02.2 MBCS와 WBCS의 동시 지원

 

아직까진 현존하는 시스템 모두가 완벽히 유니코드 기반을 지원하는 것이 아니라 문제 발생의 소지가 높기 때문에 프로그램을 MBCS, WBCS 두 가지 모두 지원하도록 구현할 필요가 있다.

최초 프로그램 구현 후 별다른 수정 없이도 MBCS, WBCS 모두를 지원하는 프로그램 작성 방법을 알아본다.

 

 

02.2.1 #include <windows.h>

 

- <windows.h> : Windows 프로그래밍에 필요한 다양한 종류의 헤더 파일을 포함
- 포함 관계 : <windows.h> ⊃ <windef.h> ⊃ <winnt.h>

 

 

 

02.2.2 Windows에서 정의하고 있는 자료형

 

- Windows에서는 typedef 키워드를 통하여 몇몇 기본 자료형에 Windows 스타일의 새로운 이름을 정의하고 있다.
- Windows 스타일의 자료형 CHAR와 WCHAR의 정의(<windows.h>를 통해 정의)
    typedef char CHAR;
    typedef wchar_t WCHAR;
- 문자열의 주솟값을 저장할 수 있는 Windows 스타일의 자료형 정의(매크로 CONST는 <windef.h>, 나머지는 <winnt.h>)


전처리문 #define을 이용해 const 키워드를 CONST로 사용할 수 있도록 매크로 선언이 되어있고,
typedef [이름] [별칭]으로 각 이름별 형식을 정의한 것을 알 수 있다.

 

(이는 선언의 용이성과 확장성을 고려한 프로그래밍 방식이다.)

 

※ 참고!

LP
: long pointer의 약자로 .Net에서는 64bit pointer, VC++6.0과 그 이전 버전에서는 32bit pointer를 나타낸다.

C
: constant의 약자로 함수의 내부에서 인자 값을 변경하지 말라는 의미로 사용한다.

STR
: string의 약자이다. char형 배열.

W
: wide char을 의미한다. UNICODE 기반에서 사용한다.

T
: tchar의 약자이다.

 

ex) LPCWSTR = const wchar *

 

[예제 2-5]

 

 

유니코드 기반으로 작성된 예제이다. 문자열을 담는 변수와 담기는 문자열의 문자셋을 통일시켜 주었다.

 

 

이 코드를 잘 보면 책에는 원래
9    LPSTR str1 = "SBCS Style String 1";
10  LPWSTR str2 = L"WBCS Style String 1";
위와 같이 코드가 작성되어 있다. 그런데 이렇게 할 경우 에러가 난다.(왜 책에는 에러에 대한 얘기가 없는지 모르겠다)
그 이유는 오른쪽에 대입할 문자열이 각각 CONST CHAR * / CONST WCHAR *형이기 때문이다.
이를 LPSTR / LPWSTR형의 변수에 대입해줄 때 반드시 강제 형 변환을 거친 후 대입해야 에러가 나지 않는다.

 

 

02.2.3 MBCS와 WBCS(유니코드)를 동시에 지원하기 위한 매크로

 

 

Windows에 선언되어 있는 내용을 보기 좋은 구조로 간략화한 것이다.
이는 두 가지 문자셋을 한 번의 구현으로 동시에 지원하기 위한 매크로이다.

 

<tchar.h>에 선언되어 있는 내용 중 일부를 보기 좋은 구조로 편집해놓은 것이다.
(<tchar.h>는 <windows.h>에 포함되지 않으므로 따로 <tchar.h>를 추가해야 한다)

 

예시를 들어 위 매크로를 설명하기 위해 다음과 같이 선언된 배열이 있다고 가정하자.
    TCHAR arr[10];
만약에 UNICODE라는 매크로가 정의되어 있지 않다면, 이 배열 선언은 전처리기에 의해서 다음과 같이 변경된다.
    CHAR arr[10];
반대로 UNICODE라는 매크로가 정의되어 있다면, 전처리기에 의해서 다음과 같이 변경되며, 이는 WBCS 기반의 문자열 저장을 가능하게 한다.
    WCHAR arr[10];

 

[그림 2-1]

 

한가지 예를 더 들어보자. 다음과 같이 문자열이 선언되었다.
    _T("HANBIT");
이 매크로는 아무 조건 없이 전처리기에 의해서 다음과 같이 변경될 것이다.(#define _T(x) __T(x)에 의해서)
    __T("HANBIT");
이 문장은 _UNICODE라는 매크로가 정의되어 있지 않다면, 다음과 같이 MBCS 타입의 문자열로 변경된다.
    "HANBIT";
반대로 _UNICODE 매크로가 정의되어 있다면, 전처리기에 의해 다음과 같이 WBCS 기반의 문자열이 된다.
    L"HANBIT";

 

[그림 2-2]

 

(UNICODE와 _UNICODE 모두 유니코드 문자를 지원할 경우를 뜻한다)


다음 예제를 보자. 이 예제는 유니코드와 MBCS를 동시에 지원하는 프로그램의 예이다.

 

[예제 2-6]

 

 

실행 결과를 보면 15행의 문자열이 유니코드 기반으로 처리되었음을 알 수 있다.
정의된 매크로에 따라서 헤더 파일에 선언된 자료형의 형태가 결정되기 때문이다.
여기서 6, 7행을 주석 처리한 후 다시 컴파일 및 실행을 해보면 결과값은 "string length : 8"이 나올 것이다.

※ 참고!

만약 이 매크로를 코드 내에 명시하지 않고도 사용하고 싶다면, 메뉴의 [프로젝트] → [속성] → [C/C++] → [전처리기] → [전처리기 정의]를 눌러 확장키를 누른 후 편집에 들어간다.

 

 

전처리기 정의에서 빨간색으로 표시한 영역에 내가 사용하고자 하는 매크로를 적어놓고 맨 밑 [부모 또는 프로젝트 기본값에서 상속]을 체크한다. 이를 기본으로 설정해둔 후 값을 변경하지 않고 코드 내에서만 사용을 금지하려면, 예제 2-6의 4, 5행과 같이 #undef 지시자를 이용해 무효화 시켜주면 된다. (주석 처리 해제)

 

주의! 만약 매크로를 설정해놓고 코드 내에서 매크로를 다시 한번 정의하면 경고 메시지가 뜨므로 잘 확인해야 한다.

 

 

 

02.2.4 MBCS와 WBCS(유니코드)를 동시에 지원하기 위한 함수들

 

앞의 [예제 2-6] MBCS_WBCS1.cpp은 매크로 _UNICODE, UNICODE의 정의 유무에 따라 다른 문자셋 기반으로 컴파일 및 실행이 된다.
그러나 예제의 17행을 보면 다음과 같은 문자가 있다.
    printf("string length : %d \n", size);
이 문장은 매크로의 정의에 상관없이 MBCS 기반으로 컴파일 된다.
이 예제가 완벽히 WBCS로 돌아가게 하려면 다음과 같이 문장을 바꿔야 한다.
    wprintf(L"string length : %d \n", size);
그렇다면 매크로 정의에 따라 두 가지 문자셋 기반으로 모두 컴파일 가능하게 하려면 어떻게 해야 할까?

 

위와 같이 정의한 다음, 다음과 같은 문장을 구성한다면 _UNICODE의 정의 유무에 따라 다른 문자셋으로 컴파일된다.
    _tprintf(_T("string size : %d \n"), size);

 

 

사용자의 수고를 덜기 위해 위와 같은 문자열에 관련된 함수 매크로가 <tchar.h>에 정의되어 있다.

 

 

 

02.3 Comment

 

리버싱을 공부하던 중 시스템 프로그래밍 쪽 지식이 부족하다 느껴져 급하게 공부를 시작했다.

초반임에도 모르는 부분이 상당히 많았다. 아직 목표한 단원까지는 멀었지만 천천히, 꼼꼼히 짚고 넘어갈 생각이다.