본문 바로가기

3.구현/VC++

문자열 이야기(작성중)

문자열 이야기

작성자: Ospace(ospace114@empal.com)

History
080109 ospace 최초작성

제가 Windows 환경에서 개발하면서 가장 골치아픈 자료형이 문자열이다. 다른 자료형은 정수형과 실수형이고 값의 범위 정도만 알면 충분하다. 같은 형태 자료형이면 공통된 범위에 속한 값이면 쉽게 변환이 가능하고 사용할 수 있다. 그러나 문자열은 다르다. 물론 문자열 자체가 기본 자료형이 아니기 때문에 더욱더 그렇다고 볼 수 있다.

먼저 문자열 자료형의 종류를 살펴보고, 이들간의 변환을 살펴보겠다. 대부분의 내용는 MSDN를 참고했으며, 그외에 나름대로 시행착오로 만들어졌다.

그 다음으로 문자열간의 변환 방법을 알아보겠다.

마지막으로 문자열 관련 팁을 몇가지 넣도록 하겠다.

  • 문자열 자료형의 종류
  • 문자열간 자료형 변환 방법
  • 문자열 관련 팁

Note: 여기서는 자료형에 대해서만 다루며 문자 인코딩/디코딩에 대해서는 다루지 안는다.

문자열 자료형의 종류

char

가장 기본적인 문자 자료형으로 char이 있다. 이를 이용해서 문자열을 표현하는 방식이 가장 일반적이다. 보통 C-style 문자열이라고 한다.

char* str1; // 포인터 문자열로 사용 전에 반드시 메모리 할당하고 다 사용한 후에 해제해야한다.
char str2[256]; // 배열형 문자열

문제점

char은 Ascii 문자를 저장할 수 있는 것으로 영어권 외에 문자저장에는 적합하지 않다.

TCHAR

윈도우에서 가장 일반적인 문자열로서 Win32 API 호출시 많이 사용되는 문자열 형식이다. 물론 많이 보지 못할 수도 있다. 만약 char이용한 문자열 사용한다면 TCHAR로 사용하는 문자열 사용을 강력 추천한다. char을 이용하는 방법과 동일하게 사용하면 된다.

char와 차이점은 없고 wchar에 대한 지원이 매크로를 통해서 가능하게 되었다. 아래에 참고로 TCHAR에 타입 정의를 살펴보면 쉽게 알 수 있다.

#ifdef _UNICODE
#typedef wchar TCHAR
#else
#typedef char TCHAR
#endif

즉 단지 char 혹은 wchar을 TCHAR로 치환해서 사용한다고 보면 된다. 그래서 컴파일 환경에 따라서 아스키만 사용하느냐 유니코드도 지원되는냐를 알아서 결정해준다.
TCHAR에서 파생된 여러 문자열 타입 정의들이 있다. 다음에 그 예이다.

  • WCHAR: wchar을 타입 정의. wchar대신에 WCHAR로 사용한다는 의미
  • LPSTR: char 포인터 문자열. 풀어스면 "char* "가 된다.
  • LPWSTR: 포인터 WCHAR일뿐 풀어쓰면 "WCHAR* " 정도가 된다.
  • LPCWSTR: 상수 LPWSTR이다. 풀어쓰면 "const LPWSTR"가 된다.
  • TCHAR: 현재 언어환경에 따라서 char 혹은 wchar가 되기도 한다.
  • LPTSTR: 포인터형 TCHAR 문자열이다. 즉 "TCHAR* "이 된다.
  • LPCTSTR: 상수 LPTSTR 문자열이다. 즉 "const LPTSTR"이 된다.

이때 일반적인 문자열 대입은 어떻게 할까? 아래와 같이 기존 C-style형태로 하면 될까?

예)

TCHAR str[6] = "abc가나다";

위의 예제는 때에 따라서 잘되기도 하고 안되기도 한다. 문제는 TCHAR가 char형으로 사용되는 경우 한글은 "가나다"글자가 어떻게 입력되는지가 문제이다. 그러면 문자열 값을 넣을 경우 사용하는 문자열 셋 환경에 따르면 어떻게 될지 모른다. 이때 필요한 것이 문자열 값을 위한 매크로이다.

기본적인 메크로는 _T, __T, TEXT, _TEXT, __TEXT가 있다. 모두 같은 의미이다. 자신이 편한걸 아무나 사용하면 된다. 사용하는 방법은 다음과 같다.

예)

TCHAR str[6] = _T("abc가나다");

만약 WCHAR인 문자열인 경우는 현재 문자열 셋이 아스키라고 해도 강제로 문자열 값이 유니코드로 저장되어야 한다. 이때 사용하는 매크로가 L이 있다. 이 매크로는 앞의 매크로와는 틀리게 괄호("()")를 사용하지 안는다.

WCHAR str[6] = L"abc가나다";

Note: 추가로, wchar은 vc환경에 따라서 내부 자료형으로 선언되어 사용하거나 unsigned short 타입으로 사용된다. 그래서 때에 따라서 타입간 호환이 안되는 경우도 있으니 참고하길 바란다.
정확한 타입명은 wchar_t(이하 wchar대신에 wchar_t 명칭을 사용)이며 이에 대한 판단은 strsafe.h파일에 있다. 해당 내용은 다음과 같다.

// strsafe.h 파일 내용
#if !define(_WCHAR_T_DEFINED) && !define(_NATIVE_WCHAR_DEFINE)
typedef unsigned short wchar_t
#define _WCHAR_T_DEFINED
#endif

Caution: 그리고, 앞에서 쉽게 이해하기 위해서 유니코드 환경만 고려했지만 MBCS(Multi-Byte Character Set) 환경도 있다. 이때 매크로에의한 타입 정의가 약간 달라지는 부분이 발생한다. TCHAR가 환경에 따라서 바뀐다고 했는데 MBCS인 경우는 char형으로 사용된다. MBCS은 다국에 지원위한 문자열셋인데 TCHAR가 char형인 것은 약간 이상해질 수 있지만 주의하길 바란다.
나머지 LPTSTR과 LPCTSTR은 TCHAR의 확장이므로 모두 char형을 가지게 된다.

OLECHAR

이 문자열은 앞의 C-style문자열과 같은 형태이다. 단지 자동화 인터페이스에서 자주 사용된다. 이것 역시 타입 정의에 의해서 일반적으로 wchar_t로 사용된다. 일반 아스키 기반인 경우는 "#define OLE2ANSI"해주면 char형을 사용한다. 최근에 다국어를 많이 사용하므로 아스키를 사용할 경우는 거의 없으므로 OLE2ANSI를 거의 사용하지 않은다. C-style과 같은 형태라고 해서 WCHAR대신에 OLECHAR로 사용하는 것은 비추천이다.

  • OLECHAR: 풀어서 쓰면 "wchar_t" 형태
  • LPOLESTR: 풀어서 쓰면 "OLECHAR*" 형태
  • LPCOLESTR: 풀어서 스면 "const OLECHAR*" 형태

앞의 _T, TEXT처럼 OLECHAR에 대입할 문자열 값을 다룰 때 사용할 매크로가 있다. OLESTR이 있다. 사용법은 _T, TEXT 등과 같다.

OLECHAR str[6] = OLECHAR("abc가나다");

BSTR

COM 인터페이스서 사용되는 문자열이다. 이 문자열 타입은 파스칼-style과 C-style의 혼합된 문자열 타입니다. 일반적으로 C-style에서는 문자열 끝을 표시할 때에 제로바이트("/0")가 사용된다. BSTR에서는 문자열 시작 전에 문자열 길이(바이트 단위)를 저장하고 그 다음에 문자열 값들이 온다. 물론 문자열 끝에는 종결 문자인 제로 바이트("/0")가 온다. 문자열 길이에는 종결문자가 포함되지 안는다.
이렇게 문자열 앞에 문자열 길이가 오는 것은 COM특성상 다른 곳으로 마샬링해서 데이터를 보낼때 데이터의 크기를 알아야할 필요성이 있다. 이를 위한 정보로 제공된다.

BSTR도 매크로에 의해서 다음과 같이 타입 정의가 되었다.

typedef OLECHAR* BSTR;

조금 이상하다. OLECHAR로 BSTR이 선언되었다. 즉 wchar_t형 문자열이라 볼 수 있다. 그리고 OLECHAR를 포인터로 사용했는데 LPOLESTR로 사용하지 않은 이유는 무엇일까?
BSTR대신에 LPOLESTR로 사용해도 컴파일에는 문제가 없다. 그러나 BSTR 문자열을 받는 함수에서 문자열 값 앞에 길이 정보가 포함되어있다고 가정하에 사용되므로 잘못된 데이터로 처리될 수 있다. 그리고 COM에서 마샬링으로 데이터 전달하는 경우 잘못된 길이의 데이터가 전송될 수 있다.
즉 BSTR타입은 따로 정의해두고 사용하는게 타입 사용에 안전하다.

BSTR은 저장되는 데이터 포멧이 다르므로 그 사용법도 C-style과는 다르다. 이를 위해 제공되는 중요한 함수 두가지가 있다. 즉 문자열 할당과 할당된 문자열 해제하는 함수이다. 간단한 예를 보면 쉽게 이해할 수 있다.

BSTR bstr = NULL;
bstr = SysAllocString(L"abc가나다"); // 문자열 할당

if(bstr == NULL) {
// BSTR 문자열 할당 실폐
}

// 문자열 처리
SysFreeString(bstr); // 문자열 해제

사용하기가 조금 번거롭다. 그렇다고 이렇게만 사용해야 하는가? 물론 아니다. 역시 똑똑한 사람들이 우리를 위해서 도우미 클래스(Wrapper class)가 있다. (주의: 도우미 함수는 본인이 임의로 붙였다. 정확한 명칭은 랩퍼클래스라고 한다.)

_bstr_t

BSTR 문자열을 다루기 위한 랩퍼 클래스이다. 저장된 문자열 내부에 직접 접근하는 방법은 없다. 그럼 먼저 실제 사용방법을 보자.

// BSTR 문자열 생성
_bstr_t bstr1 = "abc"; // LPCSTR 문자열에서 생성
_bstr_t bstr2 = L"abc가나다"; // LPCWSTR 문자열에서 생성
_bstr_t bstr3 = bstr1; // 다른 _bstr_r에서 생성, 단순 대입처럼 보면 됨

_variant_tv = "test";
_bstr_t bstr4 = v;// _variant_t에서 생성

// 원하는 자료형으로 문자열 추출
LPCSTR str1 = bstr1;
LPCSTR str2 = (LPCSTR) bstr1;
LPCWSTR str3 = bstr1;
LPCWSTR str4 = (LPCWSTR) bstr1;
BSTR bstr = bstr1.copy(); //복사
SysFreeString(bstr);// 앞에서 BSTR사용한 것 처럼 반드시 해제해줘야 한다.

한가지 유념할 점이 있다. 지금 추출되는 문자열이 모두 상수형이다. 즉 내부에 저장된 문자열 포인터를 리턴해준다. 물론 상수형을 제거해서 문자열을 수정할 수 있다. 만약 이렇게 되면 수정된 문자열 길이와 기존에 문자열 길이가 다르게 되는 문제점이 발생하면서 오류가 발생할 수 있다.

CString

앞에는 일반적인 문자를 나열하는 정도로만 보면된다. 지금 부터는 이른 문자열 데이터를 감싸고 있는 클래스 들을 보겠다.

그중에서 대표적인 것이 CString 클래스로 MFC의 대표적인 클래스이다. CString은 TCHAR형 타입을 다룬다. 상당히 방대한 클래스이며 Windows 내부에 사용되는 대부분의 문자열을 처리할 수 있다. 즉 유니코드나 MBSC도 한번에 처리할 수 있다. 그러나 MFC에 종속적이라서 MFC를 사용하지 않고 개발하는 경우 사용할 수 없다. 그리고 문자열 다루기는 쉬우나 메모리 관리 효율성과 성능적인 측면에서는 떨어지는 점이 있다. 이는 본인의 경험에 의한 것이기에 환경이나 조건에 따라서 달라질 수 있겠다.

std::string, std::wsting

템플릿 라이브러리(STL)에서 제공되는 클래스이다. 이는 원래 basic_string에서 기반을 하며 보통 char형 문자열을 저장한다. 그러면 wchar_t형 문자열을 저장할 경우는 wstring을 사용한다.

문자열 포멧이 같은 형태라면 문제가 없으나 앞에서 보았듯이 전혀 다른 포멧을 가지는 경우가 있다. 이때에는 전혀 다른 문자열 값이 된다.

예)

(LPCWSTR) "C:\\\\foo.txt"

CString에서는 다르다. 내부적인 형변환 연자사를 이용해서 형변환을 하게된다.

예)

CString str("C:\\\\foo.txt");
Fun((LPCTSTR) str); //이때 (LPCTSTR)은 CString 내부에 정의된 형변환 연산자이다.
반응형