🌟 멀티바이트 vs 유니코드, 도대체 뭐가 다른가?
C++ / MFC 개발을 하다 보면 char*, wchar_t*, CString, _T() 같은 게 뒤섞여서 빌드 에러가 빵빵 터진다.
오늘은 그 원인인 멀티바이트 vs 유니코드에 대해 정리해본다.
1. 컴퓨터는 문자를 어떻게 저장할까?
컴퓨터는 모든 걸 숫자 (0과 1)로 저장한다.
문자도 마찬가지다. 'A'라는 글자를 저장하려면 "'A'는 숫자 '65'야" 라고 약속을 정해야 한다.
이 약속을 인코딩(Encoding) 이라고 한다.
2. 멀티바이트 (Multi-Byte) 란?
- 문자 하나를 1바이트 또는 2바이트로 표현하는 방식
- 영어는 1바이트, 한글/한자 같은 문자는 2바이트 사용
- 대표적인 인코딩: EUC-KR, CP949
C++에서는?
char ch = 'A'; // 1바이트
char* str = "안녕"; // 한 글자가 2바이트 → 총 4바이트
문제점
- "이 문자가 1바이트짜리인지 2바이트짜리인지" 직접 판단해야 해서 복잡하다.
- 나라마다 인코딩 방식이 달라서 호환이 안 되는 경우가 많다.
3. 유니코드 (Unicode) 란?
- 전 세계 모든 문자를 하나의 표준으로 표현하는 방식
- 영어든 한글이든 일본어든 이모지든 전부 커버
- C++에서는 문자 하나를 2바이트 (wchat_t)로 표현
C++에서는?
wchar_t ch = L'A'; // 2바이트
wchar_t* str = L"안녕"; // 한 글자도 2바이트 → 총 4바이트 (동일)
장점
- 모든 문자를 동일한 크기로 처리 → 단순하고 안전
- 국제화 (다국어 지원)에 유리
4. TCHAR와 _T()가 뭔데?
Visual Studio에서는 프로젝트 설정에 따라 멀티바이트 / 유니코드 둘 중 하나를 선택할 수 이다.
그런데 코드를 두 환경 모두에서 동작하게 만들려면?
→ Microsoft가 만든 TCHAR와 _T() 매크로를 쓴다
// 프로젝트 설정이 멀티바이트라면:
// TCHAR → char
// _T("") → "문자열"
// 프로젝트 설정이 유니코드라면:
// TCHAR → wchar_t
// _T("") → L"문자열"
TCHAR str[] = _T("안녕하세요"); // 설정에 따라 자동으로 변환됨
쉽게 말하면 _T()는 "설정에 따라 알아서 맞춰줘" 라는 뜻
5. 그래서 빌드 에러가 왜 나는 거야?
유니코드 프로젝트에서 char*를 그냥 쓰면 에러가 난다.
// 유니코드 프로젝트에서 에러 발생
void SomeFunction(char* szValue);
SomeFunction("Hello"); // const wchar_t* → char* 변환 불가!
// 해결 방법 1: 함수 인자를 wchar_t*로 바꾸기
void SomeFunction(wchar_t* szValue);
// 해결 방법 2: CT2A로 변환해서 넘기기
SomeFunction(CT2A(_T("Hello"))); // wchar_t* → char* 로 변환
6. CT2A / CA2T 변환 매크로
MFC에서는 두 타입을 서로 변환하는 매크로를 제공한다.
| 매크로 | 변환 방향 | 의미 |
| CT2A | TCHAR* → char* | T(유니코드) to ANSI(멀티바이트) |
| CA2T | char* → TCHAR* | ANSI(멀티바이트) to T(유니코드) |
CString strValue = _T("Mono8");
// CString(유니코드)을 char*로 변환해서 함수에 넘길 때
SetEnumeration(CT2A(strValue), "PixelFormat");
외우는 팁: "T to A" / "A to T" → 화살표 방향으로 기억하자!
7. 정리
| 구분 | 멀티바이트 | 유니코드 |
| 문자 크기 | 1~2 바이트 | 2 바이트 고정 |
| 타입 | char | wchar_t |
| 문자열 리터럴 | "문자열" | L"문자열" |
| MFC 매크로 | - | _T("문자열") |
| VS 프로젝트 설정 | Mlti-Byte Character Set | Unicode Character Set |
🌟 멀티바이트 → 유니코드 전환할 때 실제로 겪는 에러들
프로젝트 설정을 Multi-Byte → Unicode 로 바꾸는 순간, 빌드 에러가 한 번에 쏟아진다.
실제로 겪은 에러 3가지와 해결 방법을 정리해봤다.
에러 1. char* vs wchar_t* 타입 불일치
유니코드로 바꾸면 TCHAR가 wchar_t가 되는데,
기존 코드에서 char*를 그대로 쓰고 있으면 타입이 안 맞아서 에러가 난다.
// 에러 발생
// error C2664: 'char *' 형식의 인수를 'const wchar_t *'(으)로 변환할 수 없습니다
char* strName = "Basler";
CString result = strName; // char* → CString(wchar_t) 불일치!
해결방법
// 방법 1: wchar_t* 로 바꾸기
wchar_t* strName = L"Basler";
// 방법 2: _T() 매크로 사용 (권장)
TCHAR* strName = _T("Basler");
// 방법 3: CA2T 로 변환 (char*를 그대로 써야 하는 경우)
char* strName = "Basler";
CString result = CA2T(strName); // char* → wchar_t* 변환
에러 2. _T() 매크로 관련 에러
_T()를 붙이면 유니코드 환경에서 L"문자열"로 바뀐다.
그런데 char*를 인자로 받는 함수에 _T()를 붙인 문자열을 넘기면 에러가 난다.
// 에러 발생
// error: 'const wchar_t[6]' → 'char *' 변환 불가
void SetEnumeration(char* szValue, char* szNodeName);
SetEnumeration(_T("Mono8"), _T("PixelFormat"));
// 유니코드에서 _T("Mono8") = L"Mono8" (wchar_t*)
// 근데 함수는 char* 를 원함 → 타입 불일치!
해결방법
// _T() 를 제거하고 CT2A 로 감싸기
SetEnumeration(CT2A(_T("Mono8")), ("PixelFormat"));
// ↑ wchar_t* → char* 로 변환
// 또는 처음부터 _T() 없이 char 리터럴로 넘기기
SetEnumeration("Mono8", "PixelFormat"); // 함수 자체가 char*를 받는 경우
핵심 판단 기준:
함수 인자가 char* 인가, wchar_t* 인가를 먼저 확인하자.
char* 를 먼저 받는 함수하면 → _T() 빼거나 CT2A() 로 감싸기
wchar_t* 를 받는 함수라면 → _T() 그대로 쓰기
에러 3. CT2A / CA2T 변환 에러
변환 방형을 반대로 쓰거나, 변환 없이 그냥 대입하면 에러
// 방향 반대로 쓴 경우
char* szResult = CA2T("Hello"); // A→T 인데 결과를 char*에 넣으려 함 → 에러
// 변환 없이 그냥 대입한 경우
CString strValue = _T("Mono8");
char* szValue = strValue; // CString(wchar_t*) → char* 직접 대입 불가!
해결 방법
// CT2A : CString(wchar_t*) → char* 로 변환할 때
CString strValue = _T("Mono8");
CT2A szValue(strValue); // 이렇게 변수처럼 선언해서 쓰는 것도 가능
SetEnumeration(szValue, "PixelFormat");
// 또는 인라인으로 바로 넘기기
SetEnumeration(CT2A(strValue), "PixelFormat");
// CA2T : char* → CString(wchar_t*) 로 변환할 때
char* szName = "Basler";
CString strName = CA2T(szName);
변환 방향 한눈에 정리
[ 멀티바이트 ] [ 유니코드 ]
char* → CA2T → wchar_t* (TCHAR*)
char* ← CT2A ← wchar_t* (TCHAR*)
에러 4. 실제 겪은 케이스 → tfopen vs fopen_s
CString strFile을 파일 경로로 받아서 파일을 여는 함수였는데,
멀티바이트 → 유니코드로 전환하면서 에러가 났다.
// 멀티바이트 코드 (기존)
pFile = _tfopen(strFile, _T("rb"));
// 멀티바이트 환경에서는 _tfopen → fopen 으로 동작해서 문제없었음
// 유니코드로 바꿨더니 에러 발생
fopen_s(&pFile, strFile, "rb");
// fopen_s 는 char* 만 받는데
// 유니코드 환경에서 CString = wchar_t* 이라 타입 불일치!
왜 이런 오류가 났나?
| 함수 | 인자 타입 | 비고 |
| -tfopen | TCHAR* | 프로젝트 설정에 따라 자동 대응 |
| fopen_s | char* | 항상 멀티바이트만 받음 |
| _wfopen_s | wchar_t* | 항상 유니코드만 받음 |
해결 방법
// 방법 1: CT2A 로 CString → char* 변환해서 넘기기
fopen_s(&pFile, CT2A(strFile), "rb");
// 방법 2: 유니코드 전용 함수 _wfopen_s 사용
_wfopen_s(&pFile, strFile, L"rb");
// 방법 3: _tfopen 그대로 유지 (설정에 따라 자동 대응)
pFile = _tfopen(strFile, _T("rb"));
어떤 방법이 가장 좋을까?
외부 라이브러리처럼 char*를 강제로 요구하는 경우 → CT2A 사용
유니코드 환경에서만 쓸 거라면 → _wfopen_s 사용
멀티바이트 / 유니코드 둘 다 지원해야 한다면 → _tfopen 유지가 제일 무난
🌟 전환 작업 체크리스트
- 문자열 리터럴 "문자열" → _T("문자열")로 바꾸기
- char* 변수 → TCHAR* 또는 wchar_t*로 바꾸기
- 외부 라이브러리 함수 (char* 인자) → CT2A() 로 감싸서 넘기기
- char*로 받아야 하는 경우 → _T() 제거 or CT2A() 적용
- 빌드 후 에러 메시지에서 타입 확인 → 방향에 맞게 CT2A / CA2T 선택

'MFC_C++ > Task' 카테고리의 다른 글
| MFC_C++ 스레드 (Thread) (0) | 2026.04.20 |
|---|---|
| MFC_C++ CallBack (0) | 2026.03.27 |
| MFC_C++ 윈도우 계산기 리펙토링 (0) | 2026.03.19 |
| MFC_C++ 배열 (0) | 2026.03.03 |