[Chapter 02] 아스키코드 vs 유니코드

·2022년 10월 18일

※ 아래는 윤성우 뇌를 자극하는 윈도우즈 프로그래밍 한빛미디어(주) 2022년
Chapter02(p.51 ~ 75)를 읽고 정리한 내용입니다.

01 문자셋(Character Sets)의 종류와 특성


문자셋 : 문자들의 집합, 다시 말해서 "약속된 문자의 표현방법"

문자셋의 종류특성
SBCS(Single Byte Character Set)1바이트만을 사용 (예) 아스키 코드
MBCS(Multi Byte Character Set)문자에 따라 1~2바이트 사용
WBCS(Wide Byte Character Set)2바이트만을 사용 (예) 유니코드
(유니코드는 문자열의 끝을 의미하는 NULL 문자도 2바이트로 처리 됨)

02 MBCS 기반의 문자열의 문제점


// 예제 2-1

#include <stdio.h>
#include <string.h>

int main(void)
{
	char str[] = "ABC한글";
    int size = sizeof(str); // ABC(3바이트) + 한글(4바이트) + NULL 문자(1바이트) = 8
    int len = strlen(str);  // ABC(길이 3) + 한글(길이 4) = 7
    
    printf("배열의 크기: %d \n", size);
    printf("문자열 길이: %d \n", len);
    
    return 0;
}

<실행 결과>
MBCS 사용 시 "ABC한글"의 실제 길이는 5지만 "한글"이라는 단어의 길이가 4로 인식되어
문자열의 길이를 7로 나타냄

// 예제 2-2

#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;
}

<실행 결과>
문자열의 길이가 5인데 for 루프 내에 선언되어 있는 fputc 함수를 10번 호출해야만 정상적으로 출력됨


03 WBCS 기반의 문자열


(1) char를 대신하는 wchar_t
(2) "ABC"를 대신하는 L"ABC"
(3) strlen을 대신하는 wcslen
(4) printf를 대신하는 wprintf

// 예제 2-3

#include <stdio.h>
#include <string.h>

int main(void)
{
    wchar_t str[] = L"ABC";
    int size = sizeof(str);
    int len = strlen(str); // strlen은 SBCS기반 문자열을 처리하기 위한 함수로 에러 발생

    printf("배열의 크기: %d \n", size);
    printf("문자열 길이: %d \n", len);

    return 0;
}


// 예제 2-4

#include <stdio.h>
#include <string.h>

int main(void)
{
    wchar_t str[] = L"ABC";
    int size = sizeof(str);
    int len = wcslen(str); // strlen -> wcslen로 에러 해결

    printf("배열의 크기: %d \n", size);
    printf("문자열 길이: %d \n", len);

    return 0;
}

// 예제 2-5

#include <stdio.h>
#include <string.h>

int main(void)
{
    wchar_t str[] = L"ABC";
    int size = sizeof(str);
    int len = wcslen(str); // strlen -> wcslen

    wprintf(L"Array Size: %d \n", size); // print -> wprint
    wprintf(L"String Length: %d \n", len);

    return 0;
}

// 예제 2-6

#include <stdio.h>

int main(int argc, char* argv[])
{
	int i;
	for (i = 0; i < argc; i++)
	{
		 fputws(argv[i], stdout); // 출력 대상이 유니코드 기반이 아니므로 에러 발생

		return 0;
	}
}

int main(int argc, wchar_t* argv[])
{
	int i;
	for (i = 0; i < argc; i++)
	{
		 fputws(argv[i], stdout); // 에러는 발생하지 않으나 출력 결과에 문제가 있음 
						 // 원인 : main 함수는 프로그램 실행 시 전달되는 문자열을 MBCS 기반으로 구성함

		return 0;
	}
}

// 예제 2-7

#include <stdio.h>

int wmain(int argc, wchar_t* argv[]) // main -> wmain, char -> wchar_t
{
	int i;
	for (i = 1; i < argc; i++)
	{
		fputws(argv[i], stdout); 
		fputws(L"\n", stdout);

		return 0;
	}
}

// 예제 2-8
#include <stdio.h>
#include <windows.h>

int wmain(int argc, wchar_t* argv[])
{
	LPSTR str1 = "SBCS Style String 1"; // (1)에러 발생
	LPWSTR str2 = L"WBCS Style String 1"; // (2)에러 발생

	CHAR arr1[] = "SBCS Style String 2";
	WCHAR arr2[] = L"WBCS Style String 2";

	LPCSTR cStr1 = arr1;
	LPWSTR cStr2 = arr2;

	printf("%s\n", str1);
	printf("%s\n", arr1);

	wprintf(L"%s\n", str2);
	wprintf(L"%s\n", arr2);

	return 0;
}

 /* 책에서는 해당 코드가 잘 실행된다고 되어 있는데 실제로 해보니 두 가지 에러가 발생했다.
 (1) "const char* 형식의 값을 사용하여 LPSTR 형식의 엔터티를 초기화할 수 없습니다" 라는 에러가 뜬다.
 (2) "const wchar_t* 형식의 값을 사용하여 LPWSTR 형식의 엔터티를 초기화할 수 없습니다" 라는 에러가 뜬다.

에러 없이 실행결과를 도출 해 내기 위해 아래와 같이 코드를 변경하였다. */

int wmain(int argc, wchar_t* argv[])
{
	LPCSTR str1 = "SBCS Style String 1";
	LPCWSTR str2 = L"WBCS Style String 1";

	CHAR arr1[] = "SBCS Style String 2";
	WCHAR arr2[] = L"WBCS Style String 2";

	LPSTR cStr1 = arr1;
	LPWSTR cStr2 = arr2;

	printf("%s\n", str1);
	printf("%s\n", arr1);

	wprintf(L"%s\n", str2);
	wprintf(L"%s\n", arr2);

	return 0;
}

<실행 결과>


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


// 예제 2-9

#define UNICODE
#define _UNICODE

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

int main(void)
{
	TCHAR str[] = _T("1234567");
	int size = sizeof(str);
	printf("string length : %d \n", size);
	return 0;
}

<실행 결과>
_T("1234567")이 유니코드로 처리되었음을 알 수 있다.

#define UNICODE 대신 #undef UNICODE, #define _UNICODE 대신 #undef _UNICODE를 넣고 실행하면
아래와 같은 결과가 나온다.

// 예제 2-10

#define UNICODE
#define _UNICODE

#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"), sizeof(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("String1 + String2 : %s \n"), str3);

	return 0;
}

예제 2-10 코드 실행 시 오류가 발생된다.
아래와 같이 고치면 문자열을 입력 받기 전까지는 이상이 없으나, 입력하면 터진다.
왜 터지는지 고민 해 보았으나 잘 모르겠다. 🥲 좀 더 공부하고 다시 시도 해 보자!
-> 아래에 추가함

#define UNICODE
#define _UNICODE

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

int _tmain(int argc, TCHAR* argv[])
{
	LPCTSTR 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"), static_cast<int>(sizeof(str2)));
	_tprintf(_T("string length: %d \n"), _tcslen(pStr));

	_fputts(_T("Input String 1 : "), stdout);
	_tscanf_s(_T("%s"), str3);
	_fputts(_T("Input String 2 : "), stdout);
	_tscanf_s(_T("%s"), str4);

	_tcscat_s(str3, str4);
	_tprintf(_T("String1 + String2 : %s \n"), str3);

	return 0;
}

+ 추가

예제 2-10에서 발생된 에러 코드 C4996은 deprecated(사용이 권장되지 않는) 함수를 사용했을 때 나타나는 코드이다.
scanf는 버퍼 오버플로우라는 문제가 발생할 위험이 있으므로 더 안전한 사용을 위해
기존의 함수에_s를 붙인 코드를 사용하라고 권장하는 내용이었다.
(버퍼 오버플로우 : 할당된 크기 이상의 데이터를 저장하려고 하는 것)
scanf_s는 할당된 크기를 넘어서면 아예 저장을 하지 않는다.
예제는 할당된 크기보다 작은 양의 데이터를 입력하기 위해 만든 것으로 첫 줄에
#define _CRT_SECURE_NO_WARNINGS 를 작성해 에러를 없애고 실행하니 이상없이 작동하였다.

#define _CRT_SECURE_NO_WARNINGS
#define UNICODE
#define _UNICODE

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

int _tmain(int argc, TCHAR* argv[])
{
	LPCTSTR 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"), sizeof(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("String1 + String2 : %s \n"), str3);

	return 0;
}

<실행 결과>

0개의 댓글