Rylah's Study & Daily Life

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

Study/System Programming

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

Rylah 2022. 4. 4. 03:00

1. Windows에서의 Unicode

- 문자 셋의 종류와 특성

-> SBCS(Single Byte Character Set)

 - 문자를 표현하는데 1바이트를 사용

 - 아스키 코드

 

-> MBCS(Multi Byte Character Set)

 - 한글은 2바이트, 영문은 1바이트 사용

 - 유니코드는 MBCS가 아니다.

 

-> WBCS(Wide Byte Character Set)

 - 문자를 표현하는데 2바이트를 사용

 - 유니코드

 

 

- MBCS 기반의 문자열

예제를 하나 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
#include <stdio.h>
#include <string.h>
 
int main(void)
{
    char str[] = "ABC한글";
    int size = sizeof(str);
    int len = strlen(str);
 
    printf("배열의 크기 : %d \n"size);
    printf("문자열의 길이 : %d \n", len);
 
    return 0;
}
cs

실행 결과는 다음과 같다.

 

str[]는 왜 8바이트인 것인가?

-> ABC(3 Byte) + 한글(4 byte) + NULL (1 byte) = 8 bytes

 

이는 현재 C 프로그램에서 문자열은 MBCS 기반으로 이뤄지고 있다는 사실을 알 수 있다.

 

그런데 문자열 길이를 살펴보자.

ABC한글인데 길이가 7로 인식되고있다. 실제 길이는 5인데 MBCS 기반이라 이러한 문제점이 발생한다.

 

다른 예시를 보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
#include <stdio.h>
 
 
int main(void)
{
    char str[] = "한글입니다.";
    int i;
 
    for (i = 0; i < 5; i++)
    {
        fputc(str[i], stdout);
    }
 
    fputs("\n", stdout);
 
    for (i = 0; i < 10; i++)
        fputc(str[i], stdout);
 
    fputs("\n", stdout);
    return 0;
}
cs

여기서 for loop의 반복횟수를 주목해보자

문자열의 길이는 6이다. (. 포함)

그런데 0 1 2 3 4 즉 5번 반복했는데 '한글'만 출력이 된다.

 

문자열에 할당된 바이트가 2바이트씩이라서 읽는게 이렇게 진행되었기 때문이다.

 

그렇다면 WBCS로 코딩을 해야된다는 결론이 나온다.

 

 

- WBCS 기반의 문자열

유니코드로 프로그래밍을 하려면 다음과 같은 조건들이 필요하다.

 

- char를 대신하는 wchar_t

- "ABC"를 대신하는 L"ABC"

 

다음은 MBCS의 코드를 WBCS로 일부 변환해본 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>
 
int main()
{
    wchar_t str[] = L"ABC";
    int size = sizeof(str);
    int len = strlen(str);
 
    printf("배열의 길이 : %d\n"size);
    printf("문자열 길이 : %d\n", len);
 
    return 0;
}
cs

여기서 size인 sizeof는 타입의 길이를 반환하므로 괜찮지만

strlen의 경우에 컴파일이 되지 않게 된다.

 

왜 그럴까?

그것은 SBCS 함수를 가져다가 사용했기 때문이다.

 

SBCS 함수 WBCS 함수
strlen size_t wcslen(const wchar_t* string);
strcpy wchar_t* wcscpy(wchar_t* dest, const wchar_t* src);
strncpy wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t cnt);
strcat wchar_t* wcscat(wchar_t* dest, const wchar_t* src);
strncat wchar_t* wcsncat(wchar_t* dest, const wchar_t* src, size_t cnt);
strcmp int wcscmp (const wchar_t* s1, const wchar_t* s2);
strncmp int wcsncmp(const wchar_t* s1, const wchar_t* s2, size_t cnt);

 

그래서 결국 이 코드를 변환하면 다음과 같이 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>
 
int main()
{
    wchar_t str[] = L"ABC";
    int size = sizeof(str);
    int len = wcslen(str);
 
    printf("배열의 크기 : %d\n"size);
    printf("문자열 길이 : %d\n", len);
 
    return 0;
}
cs

우리가 원하는 문자열 길이인 3이 나왔다.

유니코드로 정상적으로 길이를 읽은 것을 확인할 수 있다.

 

- 완전한 유니코드 기반 : First Step

또다른 SBCS함수와 WBCS 기반 함수의 차이를 알아보자

SBCS 함수 WBCS 함수
printf int wprintf(const wchar_t* format [, argument]....);
scanf int wscanf(const wchar_t* format [, argument]....);
fgets wchar_t* fgetws(wchar_t* string, int n, FILE* stream);
fputs int fputws(const wchar_t* string, FILE* stream);

예제를 변경해보자

 

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
 
int main(void)
{
    wchar_t str[] = L"ABC";
    int size = sizeof(str);
    int len = wcslen(str);
 
    wprintf(L"Array Size : %d\n"size);
    wprintf(L"String Length : %d\n", len);
    return 0;
}
cs

여기서 wprintf나 wputws와 같은 함수에서 유니코드 기반으로 한글을 출력하려면 다음과 같은 함수 호출이 필요하다.

 

1
_wsetlocale (LC_ALL, L"korean"); // #include "locale.h"
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>
#include <locale.h>
 
int main(void)
{
    wchar_t str[] = L"ABC";
    int size = sizeof(str);
    int len = wcslen(str);
    _wsetlocale(LC_ALL, L"korean"); // 
    wprintf(L"Array Size : %d\n"size);
    wprintf(L"String Length : %d\n", len);
    wprintf(L"한글 출력 테스트\n");
    return 0;
}
cs

 

- 완전한 유니코드 기반 : Second Step

사실 위의 예제는 완전한 유니코드 기반이 아니다. 다음 예제를 통해서 알아보자.

 

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main(int argc, char* argv[])
{
    for (int i = 0; i < argc; i++)
        fputws(argv[i], stdout);
 
    return 0;
}
cs

여기서도 Argument를 받는 부분에서 argv의 char*와 wchar_t와의 자료형이 맞지 않은 문제를 확인할 수 있다.

 

왼쪽 부분 해설

1. 프로그램 함수 이름 main

2. 문자열은 MBCS기반으로 전달이 된다.

 

오른쪽 부분 해설

1. wmain이 프로그램 함수 이름

2. 이 함수는 유니코드 기반으로 구성된다는 것

3. 문자열 기반이 유니코드(WBCS) 기반

 

wmain으로 된 예시를 보자

 

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int wmain(int argc, wchar_t* argv[])
{
    for (int i = 0; i < argc; i++)
    {
        fputws(argv[i], stdout);
        fputws(L"\n", stdout);
    }
 
    return 0;
}
cs

2. MBCS와 WBCS의 동시 지원

앞서 살펴봤듯이 MBCS와 WBCS를 어떻게 하냐에 따라 함수 구성이나 기타 등등 많은 것이 달라짐을 알 수 있었다.

그런데 유니코드로 할 것이냐 MBCS로 할 것이냐 제각각 프로그램을 만들 것인가 고민이 많을 것이다.

둘다 지원하면 되지 않을까?

 

#include <windows.h>

윈도우에서 기본적으로 항상 포함해야되는 헤더파일이다. 이러한 파일들이 windows.h에 정의되어 있다.

정확하게는 winnt.h에 정의 되어 있는데 windef.h가 winnt.h를 포함하고 있고 windows.h는 windef.h를 포함하고 있다.

 

 

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

자료형 windows.h
typedef char CHAR;
typedef wchar_t WCHAR;
#define CONST const
typedef CHAR * LPSTR;
typedef CONST CHAR * LPCSTR;
typedef WCHAR * LPWSTR;
typedef CONST WCHAR * LPCWSTR;

 

Windows에서는 int, char과 같은 기본 자료형도 Windows 스타일로 정의하는 이유는?

1. 선언의 편리성

-> typedef는 복잡한 선언을 단순화해준다. (Modern C++은 using 사용)

1
typedef unsigned int size_t
cs

unsigned int라는 긴 문장을 size_t로 줄여버린 것이다.

 

2. 확장의 용이성

 이것은 #define 혹은 using과 같은 것이 사용되는 의미와 비슷하다.

예를 들어 unsigned short 형으로 선언한 변수를 모두 unsigned int로 변경하기로 했을 때 변경하기 용이하다.

1
typedef unsigned char STR_LEN
cs

 

이제 모든 코드가 유니코드 기반의 WBCS로 작성해보자

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <Windows.h>
 
 
int wmain(int argc, wchar_t* argv[])
{
    LPSTR str1 = "SBCS Style String 1";
    LPWSTR str2 = L"WBCS Style String 1";
 
    CHAR arr1[] = "SBCS Style String 2";
    WCHAR arr2[] = L"WBCS Style String 2";
 
    LPCSTR cStr1 = arr1;
    LPCWSTR cStr2 = arr2;
 
    printf("%s\n", str1);
    printf("%s\n", arr1);
 
    wprintf(L"%s\n", str2);
    wprintf(L"%s\n", arr2);
    
    return 0;
}
cs

이 코드를 작성했는데 오류가 뿜뿜하는 것은 Visual Studio가 엄격하게 오류를 잡기 때문이다.

준수모드를 아니오로 변경

멀티바이트 문자 집합 사용

 

이정도로 해결 할 수 있다.

동시에 지원하려면 #IFDEF #ELSE #ENDIF를 활용하는 것도 방법이다.

예시는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <Windows.h>
#include <tchar.h>
 
#ifdef UNICODE
typedef WCHAR    TCHAR;
typedef LPWSTR    LPTSTR;
typedef LPCWSTR    LPCTSTR;
#else
typedef CHAR    TCHAR;
typedef LPSTR    LPTSTR;
typedef LPCSTR    LPCTSTR;
 
#endif // UNICODE
 
cs

tchar.h는 windows에 포함되어 있지 않다.

 

다음과 같은 흐름으로 이어진다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
#define UNICODE
#define _UNICODE
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
 
int wmain()
{
    TCHAR str[] = _T("1234567");
    int size = sizeof(str);
    printf("string length : %d \n"size);
    return 0;
}
cs

 

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
#define UNICODE
#define _UNICODE
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
 
int _tmain(int argc, TCHAR* argv[])
{
    LPTSTR str1 = _T("MBCS or WBCS 1");
    TCHAR str2[] = _T("MBCS or WBCS 2");
    TCHAR str3[100];
    TCHAR str4[50];
 
    LPCTSTR pStr = str1;
 
    _tprintf(_T("String Size : %d\n"), sizeof(str2));
    _tprintf(_T("String Length : %d\n"), _tcslen(pStr));
 
    _fputts(_T("Input String 1 : "), stdout);
    _tscanf(_T("%s"), str3);
    _fputts(_T("Input String 2 : "), stdout);
    _tscanf(_T("%s"), str4);
 
    _tcscat(str3, str4);
    _tprintf(_T("String 1 + String 2 : %s \n"), str3);
 
 
    return 0;
}
cs

정리

1. SBCS, MBCS, WBCS

- SBCS는 문자를 표현하는데 1바이트

- MBCS는 문자를 표현하는데 1바이트 혹은 2바이트

- WBCS는 문자를 표현하는데 2바이트를 사용한다.

- 유니코드는 WBCS

 

2. 유니코드 문자열 처리 함수

- ANSI 표준 문자열 처리함수와 다르게 유니코드용 문자열 처리 함수가 따로 있다.

 

3. UNICODE, _UNICODE

- 매크로 UNICODE와 _UNICODE 정의 유무에 따라서 함수 선언 형태가 달라진다. 

이를 이용해서 MBCS와 WBCS 양쪽 모두 컴파일 가능한 프로그램을 만들 수 있다.

 

4. 유니코드 방식과 MBCS 방식을 모두 지원하는 main

- MBCS : main // WBCS : wmain

- 통합 지원 : _tmain

 

'Study > System Programming' 카테고리의 다른 글

04. 컴퓨터 구조 (2)  (0) 2022.04.05
03. 64비트 기반 프로그래밍  (0) 2022.04.04
01. 시스템 프로그래밍 시작  (0) 2022.04.03