Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)
(시리즈 글이 5개 있습니다.)
Windows: 265. Win32 API의 W(유니코드) 버전은 UCS-2일까요? UTF-16 인코딩일까요?
; https://www.sysnet.pe.kr/2/0/13777

C/C++: 181. C/C++ - 소스코드 파일의 인코딩, 바이너리 모듈 상태의 인코딩
; https://www.sysnet.pe.kr/2/0/13778

C/C++: 182. 윈도우가 운영하는 2개의 Code Page
; https://www.sysnet.pe.kr/2/0/13785

Windows: 267. Win32 API의 A(ANSI) 버전은 DBCS를 사용할까요?
; https://www.sysnet.pe.kr/2/0/13791

C/C++: 183. C++ - 윈도우에서 한글(및 유니코드)을 포함한 콘솔 프로그램을 컴파일 및 실행하는 방법
; https://www.sysnet.pe.kr/2/0/13793




C++ - 윈도우에서 한글(및 유니코드)을 포함한 콘솔 프로그램을 컴파일 및 실행하는 방법

(설명을 집중하기 위해 한글 윈도우보다는 영문 윈도우를 기준으로 설명합니다.)

영문 윈도우에서 C++ 프로그램을 다음과 같이 만들고,

#include <iostream>

int main()
{
    std::cout << "안녕하세요\n";
}

컴파일 후 실행하면 결과가 어떻게 나올까요?

이를 위해 우선 소스코드 파일을 저장하는 단계부터 살펴봐야 합니다. 일단 한글이 포함된 소스코드는 (영문 윈도우의 기본 코드 페이지인) CP437/CP1252 상태로는 저장 자체가 안 됩니다. 반면 CP949(비주얼 스튜디오의 경우 "Korean - Codepage 949")로 저장하면 어떨까요?

물론 949 코드 페이지에 따른 문자 셋으로 저장은 됩니다. 문제는 해당 파일을 비주얼 스튜디오에서 다시 열었을 때 "안녕하세요" 문자열이 깨진다는 점입니다. 왜냐하면, 비주얼 스튜디오는 해당 파일을 (BOM 없는) UTF-8로 판정해 읽기 때문입니다.

대신, 에디터 화면에서 한글이 깨지는 문제를 감수한다면 빌드 및 실행은 문제가 없습니다. 물론, 실행할 때 코드 페이지를 반드시 949로 맞춰주어야만 한글이 정상적으로 출력됩니다.

// 현재 코드 페이지는 영문 윈도우의 경우 437
C:\temp\ConsoleApplication1\x64\Debug> chcp
Active code page: 437

// 437 코드 페이지에서는 949로 저장한 문자 셋이 깨짐
C:\temp\ConsoleApplication1\x64\Debug> ConsoleApplication1.exe
╛╚│τ╟╧╝╝┐Σ

// 반면, 949 코드 페이지로 변경하면,
C:\temp\ConsoleApplication1\x64\Debug> chcp 949
Active code page: 949

// 한글도 정상 출력
C:\temp\ConsoleApplication1\x64\Debug> ConsoleApplication1.exe
안녕하세요

혹은, 저 chcp를 코드 내에서 처리해도 무방합니다.

#include <iostream>
#include <Windows.h>

int main()
{
    SetConsoleOutputCP(949);
    // 또는, 
    // system("chcp 949");

    std::cout << "¾È³çÇϼ¼¿ä"; // 출력 결과: 안녕하세요
}




당연히 저렇게 소스코드 내의 문자가 깨진 상태로 개발을 하는 것은 불편할 수밖에 없습니다. 따라서, 이런 경우 현실적으로는 유니코드로 저장하는 것이 올바른 선택입니다.

// UTF-8로 저장

#include <iostream>

int main()
{
    std::cout << "안녕하세요\n";
}

하지만, 이 상태는 "소스코드 파일의 인코딩" 단계를 해결한 것일 뿐, "바이너리 모듈 상태의 인코딩"은 전혀 다른 문제가 됩니다.

즉, C++ 컴파일러는 입력 파일 자체는 정상적으로 해석을 하지만, 출력은 (시스템의 코드 페이지인) CP1252로 처리하기 때문에 이번엔 실행 시 (깨지는 정도가 아닌) 단순히 "?????"로 출력됩니다. 왜냐하면, UTF-8로 저장된 "안녕하세요" 문자에 대해 CP1252 문자 셋으로는 표현이 안 되기 때문에 바이너리에 fallback 문자로 출력을 해버린 것입니다.

따라서, 바이너리에도 UTF-8 인코딩을 이어가려면 컴파일러에게 이 사실을 알려야 합니다.

// 현재의 파일 자체는 UTF-8로 저장

// 실행 모듈에 utf-8 인코딩 지시
#pragma execution_character_set( "utf-8" )

#include <iostream>

int main()
{
    std::cout << "안녕하세요\n";
}

그러니까 ^^ 바로 여기까지가 지난 글에서 다뤘던 내용입니다.

C/C++ - 소스코드 파일의 인코딩, 바이너리 모듈 상태의 인코딩
; https://www.sysnet.pe.kr/2/0/13778




다시 한번 정리해 보면, 현실적으로 C++ 프로그램은 UTF-8로 저장하고, 컴파일러에게 바이너리에도 UTF-8로 저장하라는 지시를 포함시키는 것을 기본 방향으로 삼아야 합니다.

자, 그렇게 해서 "바이너리" 모듈은 준비가 되었습니다. 그렇다면 이제 저렇게 utf-8을 따르는 바이너리 모듈을 실행하면 어떤 결과가 나올까요?

C:\temp\ConsoleApplication1\x64\Debug> ConsoleApplication1.exe
안녕하세요

보는 바와 같이 그래도 한글이 깨집니다. 이유는 간단합니다, 영문 윈도우의 콘솔 화면은 CP437을 따르기 때문에 UTF-8로 출력된 문자열은 저렇게 깨져 나오는 것입니다. 물론, 저 프로그램을 한글 윈도우에서 실행해도, (CP949 코드 페이지는 UTF-8을 이해할 수 없으므로) 역시 한글은 깨집니다.

이 문제를 해결하려면, 단순히 콘솔 화면의 코드 페이지를 65001로 바꾸기만 하면 됩니다.

// 현재의 파일 자체는 UTF-8로 저장

// 실행 모듈에 utf-8 인코딩 지시
#pragma execution_character_set( "utf-8" )

#include <iostream>
#include <Windows.h>

int main()
{
    SetConsoleOutputCP(65001);

    // 또는 아래와 같이 system 명령어를 수행해도 되지만,
    // system("chcp 65001 > nul");
    // SetConsoleOutputCP와 다른 점이 있다면 응용 프로그램이 종료했을 때 여전히 65001로 남아있게 되므로
    // 다시 코드 페이지를 돌려놓는 과정이 필요함.

    std::cout << "안녕하세요\n";
}

// 현재 코드 페이지와 무관하게,
C:\temp\ConsoleApplication1\x64\Debug> chcp
Active code page: 437

// utf-8을 지원하는 응용 프로그램의 출력이 정상적으로 나옴
C:\temp\ConsoleApplication1\x64\Debug> ConsoleApplication1.exe
안녕하세요




이 외에도 또 다른 방법으로 한글(및 유니코드)를 출력하는 방법이 있는데요, 바로 W 버전을 사용하는 것입니다. 이를 위해 C++ 소스코드를 char 대신 wchar_t로 바꿔 작성하면 되는데요,

#include <iostream>

int main()
{
    std::wcout << L"Hello, 안녕하세요\n";
    std::wcout << L"World\n";
}

그런데, 위와 같이 바꿔도 실행해 보면 화면에 "Hello,"만 출력될 뿐 한글 문자는 아예 보이지도 않습니다.

C:\temp\ConsoleApplication1\x64\Debug> ConsoleApplication1.exe
Hello,

// 재미있는 건, 한글 출력을 못한 그 순간부터는 모든 출력을 실패(?)하게 됩니다.
// 그래서 "World"도 출력이 안 된 것입니다.

도대체 왜 이런 걸까요? 이유는, Text 모드에 있습니다.

C++ - 파일에 대한 Text 모드의 "translated" 동작
; https://www.sysnet.pe.kr/2/0/13766

C++ - _O_WTEXT, _O_U16TEXT, _O_U8TEXT의 Unicode stream 모드
; https://www.sysnet.pe.kr/2/0/13768

C/C++: 180. C++ - 고수준 FILE I/O 함수에서의 Unicode stream 모드(_O_WTEXT, _O_U16TEXT, _O_U8TEXT)
; https://www.sysnet.pe.kr/2/0/13776

기본적으로 C++ 프로그램은 표준 출력(stdout)에 대해 _O_TEXT 모드로 동작하기 때문에, UTF-16 문자열의 출력이 ANSI로 자동 번역이 돼 정상적인 한글 출력이 안 되는 것입니다.

따라서, 이런 경우에는 명시적으로 표준 출력을 UTF-16 모드로 변경해 주어야 합니다.

#include <iostream>
#include <io.h> // _setmode
#include <fcntl.h> // _O_U16TEXT

int main()
{
    int old_mode = _setmode(_fileno(stdout), _O_U16TEXT); // 표준 출력을 UTF-16 모드로 변경
    wprintf(L"old_mode: 0x%x\n", old_mode); // 0x4000 == _O_TEXT
    std::wcout << L"Hello, 안녕하세요\n"; // 또는 아예 WriteConsoleW와 같은 Win32 API를 사용하거나!
    std::wcout << L"World\n";
}

/* 출력 결과:
old_mode: 0x4000
Hello, 안녕하세요
World
*/

또는, 원한다면 (utf-8 인코딩 모드인) _O_U8TEXT 모드로 변경할 수 있습니다.

#include <iostream>
#include <io.h> // _setmode
#include <fcntl.h> // _O_U8TEXT

int main()
{
    _setmode(_fileno(stdout), _O_U8TEXT);
    std::wcout << L"Hello, 안녕하세요\n";
    std::wcout << L"World\n";
}

위의 코드가 재미있는 점이 하나 있는데요, 얼핏 코드 페이지를 65001로 바꿔야만 할 것 같은데 그렇게 안 해도 한글이 잘 출력된다는 점입니다. 출력을 redirect하면 분명히 utf-8로 인코딩된 것이 맞는데요,

C:\temp\ConsoleApplication1\x64\Debug> ConsoleApplication1.exe > test.txt

// 코드 페이지는 437,
C:\temp\ConsoleApplication1\x64\Debug> chcp
Active code page: 437

// 따라서 한글이 깨져 나오고,
C:\temp\ConsoleApplication1\x64\Debug> type test.txt
Hello, 안녕하세요
World

// 65001 코드 페이지에서만 정상적으로 한글 출력
C:\temp\ConsoleApplication1\x64\Debug> chcp 65001
Active code page: 65001

C:\temp\ConsoleApplication1\x64\Debug> type test.txt
Hello, 안녕하세요
World

음... 뭔가 ^^; 있긴 하겠지만 현재는 저것까지 설명은 안 됩니다. (혹시 아시는 분은 덧글 부탁드립니다. ^^)




참고로, C#의 유니코드 대응과 비교해 보는 것도 좋을 듯합니다. ^^

기본 해석은, "소스코드 파일의 인코딩", "바이너리 모듈 상태의 인코딩", "실행 환경" 3가지를 고려해야 한다는 점은 같은데요, 우선 C#은 소스코드 파일을 기본적으로 비주얼 스튜디오 환경이 UTF-8로 저장합니다. 그렇다면 파일의 문자 셋 해석 문제는 자연스럽게 해결됩니다. 이후, C# 컴파일러는 소스코드의 모든 문자열을 UTF-16으로 변환해 바이너리에 포함시킵니다. 따라서, 바이너리 모듈 상태의 인코딩도 문제가 없습니다.

실행 시에는 어떨까요? C#은 C++이 가지는 file descriptor, FILE Stream 등의 개념을 배제하고 운영체제에서 제공하는 함수를 사용하므로, 즉 윈도우의 경우에는 기본적으로 W 버전의 API를 사용해 I/O를 수행하기 때문에 이것 역시 유니코드 출력에 문제가 없습니다.

따라서, C#으로는 아무 생각 없이 코딩을 해도 기본적으로 한글이 깨지는 등의 문제가 발생하지 않는 것입니다.
(물론, 기존 코드 페이지로 인코딩된 파일을 I/O 할 때는 문제가 발생합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 10/30/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  52  53  54  55  56  57  [58]  59  60  ...
NoWriterDateCnt.TitleFile(s)
12491정성태1/16/202117789.NET Framework: 1008. 배열을 반환하는 C# COM 개체의 메서드를 C++에서 사용 시 메모리 누수 현상 [1]파일 다운로드1
12490정성태1/15/202116938.NET Framework: 1007. C# - foreach에서 열거 변수의 타입을 var로 쓰면 object로 추론하는 문제 [1]파일 다운로드1
12489정성태1/13/202119038.NET Framework: 1006. C# - DB에 저장한 텍스트의 (이모티콘을 비롯해) 유니코드 문자가 '?'로 보인다면? [1]
12488정성태1/13/202118219.NET Framework: 1005. C# - string 타입은 shallow copy일까요? deep copy일까요? [2]파일 다운로드1
12487정성태1/13/202116674.NET Framework: 1004. C# - GC Heap에 위치한 참조 개체의 주소를 알아내는 방법파일 다운로드1
12486정성태1/12/202118422.NET Framework: 1003. x64 환경에서 참조형의 기본 메모리 소비는 얼마나 될까요? [1]
12485정성태1/11/202118583Graphics: 38. C# - OpenCvSharp.VideoWriter에 BMP 파일을 1초씩 출력하는 예제 [2]파일 다운로드1
12484정성태1/9/202120214.NET Framework: 1002. C# - ReadOnlySequence<T> 소개파일 다운로드1
12483정성태1/8/202116446개발 환경 구성: 521. dotPeek - 훌륭한 역어셈블 소스 코드 생성 도구
12482정성태1/8/202118185.NET Framework: 1001. C# - 제네릭 타입/메서드에서 사용 시 경우에 따라 CS8377 컴파일 에러
12481정성태1/7/202118285.NET Framework: 1000. C# - CS8344 컴파일 에러: ref struct 타입의 사용 제한 메서드파일 다운로드1
12480정성태1/6/202120981.NET Framework: 999. C# - ArrayPool<T>와 MemoryPool<T> 소개파일 다운로드1
12479정성태1/6/202118205.NET Framework: 998. C# - OWIN 예제 프로젝트 만들기
12478정성태1/5/202120948.NET Framework: 997. C# - ArrayPool<T> 소개파일 다운로드1
12477정성태1/5/202122034기타: 79. github 코드 검색 방법 [1]
12476정성태1/5/202118324.NET Framework: 996. C# - 닷넷 코어에서 다른 스레드의 callstack을 구하는 방법파일 다운로드1
12475정성태1/5/202122162.NET Framework: 995. C# - Span<T>와 Memory<T> [1]파일 다운로드1
12474정성태1/4/202118915.NET Framework: 994. C# - (.NET Core 2.2부터 가능한) 프로세스 내부에서 CLR ETW 이벤트 수신 [1]파일 다운로드1
12473정성태1/4/202117513.NET Framework: 993. .NET 런타임에 따라 달라지는 정적 필드의 초기화 유무 [1]파일 다운로드1
12472정성태1/3/202117490디버깅 기술: 178. windbg - 디버그 시작 시 스크립트 실행
12471정성태1/1/202118216.NET Framework: 992. C# - .NET Core 3.0 이상부터 제공하는 runtimeOptions의 rollForward 옵션 [1]
12470정성태12/30/202018346.NET Framework: 991. .NET 5 응용 프로그램에서 WinRT API 호출 [1]파일 다운로드1
12469정성태12/30/202022816.NET Framework: 990. C# - SendInput Win32 API를 이용한 가상 키보드/마우스 [1]파일 다운로드1
12468정성태12/30/202019245Windows: 186. CMD Shell의 "Defaults"와 "Properties"에서 폰트 정보가 다른 문제 [1]
12467정성태12/29/202019275.NET Framework: 989. HttpContextAccessor를 통해 이해하는 AsyncLocal<T> [1]파일 다운로드1
12466정성태12/29/202016788.NET Framework: 988. C# - 지연 실행이 꼭 필요한 상황이 아니라면 singleton 패턴에서 DCLP보다는 static 초기화를 권장 [1]파일 다운로드1
... 46  47  48  49  50  51  52  53  54  55  56  57  [58]  59  60  ...