Rylah's Study & Daily Life
02. 아스키 코드 vs 유니코드 본문
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 |