NOMO.asia

C++을 이용하여 "UTF-8로 인코딩 된 특수문자가 포함된 유니코드 문자"를 읽고 쓰기 위해 시도했던 것들 짧게 두서없이 적어본다. 한국어 일본어 정도는 어떻게 하든 쉽게 읽고 쓸 수 있는 것 같은데, 특수문자나 이모지가 포함된 경우까지 모두 커버하려니 신경써야 하는 것이 좀 있었다. 아래에서 설명하겠지만 나는 유니코드 문자열 처리에 wstring 이 만능일 줄 알고 wstring 으로 모든걸 하려고 했다가 삽질을 좀 했다. Visual Studio 2019 사용. 결론은 맨 아래에 있다.

목표

  1. C++ 언어를 사용
  2. UTF-8로 인코딩 된 특수문자가 포함된 유니코드 문자가 적힌 txt 파일을 ifstream 으로 읽어서 std::string 또는 std::wstring 에 저장하기
  3. std::string 또는 std::wstring 에 저장된 특수문자가 포함된 유니코드 문자를 txt 파일에 쓰기
  4. std::string 또는 std::wstring 에 저장된 특수문자가 포함된 유니코드 문자를 콘솔에 출력하기
  5. Windows 와 Linux 에서 모두 동작하는 코드

유니코드 테스트에 사용한 문자열

Hello|안녕|こんにちは|简体中文|繁體中文|你好|مرحبا |Olá|Привет| مرحبا |Γειά σας|שלום|~Ѧ𝙱ƇᗞΣℱԍҤ١𝔍К𝓛𝓜ƝȎ𝚸𝑄Ṛ𝓢ṮṺƲᏔꓫ𝚈𝚭𝜶Ꮟçძ𝑒𝖿𝗀ḧ𝗂𝐣ҝɭḿ𝕟𝐨𝝔𝕢ṛ𝓼тú𝔳ẃ⤬𝝲𝗓1234567890!@#$%^&*()-_=+[{]};:'",<.>/?|😀🕧💅🍃🌊

다음 링크의 것을 내가 커스터마이징 했다: https://stackoverflow.com/questions/1319022/really-good-bad-utf-8-example-test-data

파일 읽기의 경우

  1. 파일에 저장된 유니코드 문자를 읽는 것은 ifstream 이나 wifstream 모두 잘 됨
  2. ifstream 으로 파일을 읽어서 std::string 에 저장하는 경우, 디버거에서 해당 string 을 조사식으로 찍어봤을 때 "Invalid characters in string" 라고 표시되는 경우가 있다(wifstream 으로 읽어서 wstring 에 저장한 것을 string 으로 변환해도 동일). 그런데 디버거에서 이렇게 표시되더라도 실제로 데이터는 잘 저장되어 있고 문제가 있는 것은 아니다. 이 경우, 만약 해당 string 변수의 이름이 "str" 이라면 조사식에 "str, s8" 이라고 적으면 문자열을 제대로 확인할 수 있다. ofstream 으로 txt 파일에 출력해보면 정상 출력되는 것을 확인 가능하다. (cout 으로 콘솔에 출력하는 경우에는 유니코드가 지원되는 터미널을 사용해보면 정상 출력이 되는 것을 확인할 수 있다.)
    이 문제는 Visual Studio 2019 version 16.1 에서 고쳐졌다고 하는데 난 그 상위 버전을 쓰는데도 동일한 문제가 있었다 😟. 이것 때문에 string 에 유니코드 문자를 저장하면 문제가 있는 줄 알고 wstring 으로 모든걸 하려다 삽질을 좀 했다.
    - 관련 링크: https://developercommunity.visualstudio.com/t/c-debugger-does-not-display-utf-8-strings-sometime/324963
  3. 처음부터 문자열을 wstring 에 저장하거나, 후술할 방법으로 이미 읽어 string 에 저장한 문자열을 wstring 으로 변환하면 디버거에서 별도의 조작 없이도 잘 표시된다.

string 과 wstring 간 변환

  1. utfcpp 사용
    1. 관련 링크: https://github.com/nemtrif/utfcpp
    2. Windows 및 Linux 지원
    3. 몇 개 안 되는 헤더파일을 프로젝트에 포함시키는 것 만으로도 쉽게 string 과 wstring 간 변환을 할 수 있다. 오픈소스 라이선스 고지만 하면 상용 제품에도 사용 가능. 구글에서 utfcpp oss 로 검색하면 utfcpp 를 사용하는 많은 상용 프로그램이 존재함을 확인할 수 있다.
    4. 사용도 엄청 편함
    5. 가장 베스트 선택인듯
  2. codecvt 사용
    1. 관련 링크: https://stackoverflow.com/questions/2573834/c-convert-string-or-char-to-wstring-or-wchar-t
    2. Windows 및 Linux 지원
    3. codecvt 는 C++17 에서 deprecated 됨. 하지만 아직 동작하고, 이를 완벽하게 대체하는 표준 라이브러리 함수도 아직 제공되지 않고 있다. 따라서 이걸 쓰면 언젠간 나중에 코드를 다시 해야할듯. 금방 퇴사할 것이 아니라면 사용하지 말자.
  3. [Windows] Windows.h 의 MultiByteToWideChar & WideCharToMultiByte 사용
    1. 관련 링크: https://stackoverflow.com/a/42794218
    2. Windows 환경에서만 사용이 가능
    3. 첫 사용 시 조~금 복잡하다. 하지만 어차피 Copy & Paste 할 것이므로 상관 없음.
  4.  [Linux] iconv.h 관련 함수를 사용
    1. Chatgpt 한테 iconv.h 로 string 과 wstring 을 서로 변환하는 코드를 알려달라고 하면 아래와 같이 알려준다. 실제 테스트 해보지는 않음.
    2. Linux 환경에서만 사용이 가능.
    3. 그런데 경험 상 Linux 환경에서는 wstring 을 굳이 쓰지 않아도 string 만 써도 잘 된다. 굳이 이렇게 해야할까 싶음.

파일 쓰기의 경우

결론만 말하자면, Windows 환경에서 UTF-8로 유니코드 문자 깨짐 없이 파일 출력을 하려면 ofstream 으로 string 을 출력하는 것이 확실한 방법인 것 같다.

wstring 을 wofstream 으로 출력하는 것은 비추. 이유는 wstring 을 바로 출력할 때, 만약 출력 불가능한 문자를 출력하려고 시도하면 그 이후로 해당 wofstream 으로 파일이 불가능해지는 경우가 있다. locale 설정 등 이런저런 추가적인 설정 + alpha 를 하면 이 증상을 개선할 수는 있다(자세한 설명은 생략). 그런데 더 나은 출력을 위해서는 C++17 에서 deprecated 된 codecvt 를 사용해서 locale 설정을 해야하고, 이렇게 해도 한국어, 일본어 정도는 잘 출력되지만 중국어 간체, 일부 특수문자, 이모지😊 같은 것들은 깨져서 출력된다.

Linux 는 아무거나 써도 잘 된다.

콘솔 출력

콘솔 출력의 경우도 파일 출력과 동일한 이유로 Windows 환경에서 유니코드 문자 깨짐 없이 출력하려면 cout 으로 string 을 출력해야 한다. cout 은 ofstream 에 근본을 두고있고, wcout 는 wofstream 에 근본을 두고 있기 때문에 그렇다. (참고로 printf & wprintf 도 각각 cout & wcout 과 같은 결과를 보인다.)

Windows 환경에서 콘솔에 유니코드 문자를 출력하려면 Command Prompt 의 콘솔 페이지를 UTF-8로 설정해야 한다. 이를 위해서는 Windows.h 를 include 하고 SetConsoleOutputCP(CP_UTF8) 를 호출하면 된다. 관련 링크: https://stackoverflow.com/a/19071749

기본 Windows Command Prompt 의 경우 완벽한 유니코드 문자 출력을 지원하지 않아서 중국어 간체, 특수문자나 이모지의 경우 글자가 깨져서 나타난다. 내가 코드를 잘못해서 생긴 문제가 아니다!!!! 이 경우 Windows Terminal 을 사용하면 잘 된다. Windows 11 의 경우 윈도우 설정에서 Default Terminal 설정이 가능하니 이걸 Windows Terminal 로 설정하는 것을 추천. 관련 링크: https://github.com/microsoft/terminal, https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701 

Linux 는 그냥 해도 잘 된다.

결론

  1. (특히 Windows 환경에서) C++의 유니코드 핸들링은 🐕‍🦺
  2. 콘솔이나 파일에 UTF-8로 특수문자를 출력할 때는 string 과 cout, ofstream 을 쓰자. wstring 과 wcout, wofstream 을 쓰면 깨지거나 한계가 있다.
  3. string 과 wstring 간 변환을 위해서는 utfcpp를 쓰면 편하다.
  4. Windows 환경에서 특수문자가 포함된 유니코드 문자를 콘솔에 출력할 때는 유니코드가 지원되는 Windows Terminal 을 쓰자.
  5. C++23 에서 유니코드 관련 개선 & 표준 라이브러리 함수 추가가 있다는데 언제 쓸 수 있음?