Development Tip

WChars, 인코딩, 표준 및 이식성

yourdevel 2020. 12. 5. 10:47
반응형

WChars, 인코딩, 표준 및 이식성


다음은 SO 질문으로 간주되지 않을 수 있습니다. 범위를 벗어난 경우 언제든지 저에게 가라고 말씀하십시오. 여기서 질문은 기본적으로 "내가 C 표준을 올바르게 이해하고 있으며 이것이 문제를 해결하는 올바른 방법입니까?"입니다.

C (따라서 C ++ 및 C ++ 0x)의 문자 처리에 대한 이해에 대한 설명, 확인 및 수정을 요청하고 싶습니다. 우선, 중요한 관찰 :

이식성과 직렬화는 직교 개념입니다.

휴대용 사물은 C unsigned int,, wchar_t. 직렬화 가능한 것은 uint32_tUTF-8 과 같은 것 입니다. "Portable"은 지원되는 모든 플랫폼에서 동일한 소스를 다시 컴파일하고 작업 결과를 얻을 수 있지만 바이너리 표현이 완전히 다를 수 있음을 의미합니다 (예 : TCP-over-carrier pigeon). 반면에 직렬화 가능한 항목은 항상 동일한 표현을 갖습니다 . 예를 들어 Windows 데스크톱, 휴대폰 또는 칫솔에서 읽을 수있는 PNG 파일입니다. 이식 가능한 것은 I / O를 다루는 내부의 직렬화 가능한 것입니다. 이식 가능한 것은 유형이 안전하고 직렬화 가능한 것은 유형 퍼닝이 필요합니다. </ preamble>

C에서 문자 처리와 관련하여 이식성 및 직렬화와 관련된 두 가지 그룹이 있습니다.

  • wchar_t, setlocale(), mbsrtowcs()/ wcsrtombs(): 표준 C는 "인코딩"에 대해 아무것도 말하지 않는다 ; 사실, 그것은 모든 텍스트 또는 인코딩 속성에 대해 완전히 독립적입니다. 그것은 단지 "당신의 진입 점은 당신의 시스템의 모든 문자를 담을 main(int, char**)수있는 유형 wchar_t얻습니다 . 당신은 입력 문자 시퀀스를 읽고 그것들을 작동 가능한 wstring으로 만드는 함수를 얻거나 그 반대의 경우도 마찬가지입니다.

  • iconv()및 UTF-8,16,32 : 잘 정의되고 명확한 고정 인코딩간에 트랜스 코딩하는 함수 / 라이브러리. iconv가 처리하는 모든 인코딩은 한 가지 예외를 제외하고 보편적으로 이해되고 동의됩니다.

wchar_t이식 가능한 문자 유형 이있는 C의 이식 가능하고 인코딩에 구애받지 않는 세계 와 결정 론적 외부 세계 사이의 다리는 WCHAR-T와 UTF 간의 iconv 변환 입니다.

그렇다면 항상 내 문자열을 인코딩에 구애받지 않는 wstring에 내부적으로 저장하고을 통해 CRT와 인터페이스하고 직렬화에 wcsrtombs()사용해야 iconv()합니까? 개념적으로 :

                        my program
    <-- wcstombs ---  /==============\   --- iconv(UTF8, WCHAR_T) -->
CRT                   |   wchar_t[]  |                                <Disk>
    --- mbstowcs -->  \==============/   <-- iconv(WCHAR_T, UTF8) ---
                            |
                            +-- iconv(WCHAR_T, UCS-4) --+
                                                        |
       ... <--- (adv. Unicode malarkey) ----- libicu ---+

실제로는 프로그램 진입 점 (예 : C ++)에 대해 두 개의 상용구 래퍼를 작성한다는 의미입니다.

// Portable wmain()-wrapper
#include <clocale>
#include <cwchar>
#include <string>
#include <vector>

std::vector<std::wstring> parse(int argc, char * argv[]); // use mbsrtowcs etc

int wmain(const std::vector<std::wstring> args); // user starts here

#if defined(_WIN32) || defined(WIN32)
#include <windows.h>
extern "C" int main()
{
  setlocale(LC_CTYPE, "");
  int argc;
  wchar_t * const * const argv = CommandLineToArgvW(GetCommandLineW(), &argc);
  return wmain(std::vector<std::wstring>(argv, argv + argc));
}
#else
extern "C" int main(int argc, char * argv[])
{
  setlocale(LC_CTYPE, "");
  return wmain(parse(argc, argv));
}
#endif
// Serialization utilities

#include <iconv.h>

typedef std::basic_string<uint16_t> U16String;
typedef std::basic_string<uint32_t> U32String;

U16String toUTF16(std::wstring s);
U32String toUTF32(std::wstring s);

/* ... */

이것이 iconv를 사용하여 UTF에 대한 잘 정의 된 I / O 인터페이스와 함께 순수 표준 C / C ++만을 사용하여 관용적이고 이식 가능하며 보편적 인 인코딩에 구애받지 않는 프로그램 코어를 작성하는 올바른 방법입니까? (유니 코드 정규화 또는 분음 부호 대체와 같은 문제는 범위를 벗어납니다. 실제로 유니 코드를 원하는 것으로 결정한 후에 만 (다른 코딩 시스템과는 반대로) 전용 라이브러리를 사용하여 이러한 세부 사항을 처리해야합니다. 리비 쿠처럼.)

업데이트

아주 좋은 댓글을 많이 따라 몇 가지 관찰을 추가하고 싶습니다.

  • 응용 프로그램에서 명시 적으로 유니 코드 텍스트를 처리하려는 경우 iconv코어의 -conversion 부분을 만들고 UCS-4에서 내부적으로 uint32_t/ char32_t-strings를 사용해야 합니다.

  • Windows : 넓은 문자열을 사용하는 것은 일반적으로 괜찮지 만, 합리적인 멀티 바이트 콘솔 인코딩에 대한 지원이없는 것으로 보이며 mbstowcs본질적으로 쓸모 가 없기 때문에 콘솔 (그 문제에 대한 모든 콘솔)과의 상호 작용이 제한되는 것으로 보입니다. 사소한 확대보다). 예를 들어, GetCommandLineW+ 와 함께 탐색기 드롭에서 와이드 문자열 인수를 수신하면 CommandLineToArgvWWindows 용으로 별도의 래퍼가 있어야합니다.

  • 파일 시스템 : 파일 시스템은 인코딩 개념이없는 것처럼 보이며 단순히 null로 끝나는 문자열을 파일 이름으로 사용합니다. 대부분의 시스템은 바이트 문자열을 사용하지만 Windows / NTFS는 16 비트 문자열을 사용합니다. 어떤 파일이 존재하는지 발견하고 해당 데이터를 처리 할 때주의해야합니다 (예 : char16_t유효한 UTF16을 구성하지 않는 시퀀스 (예 : 네이 키드 대리)는 유효한 NTFS 파일 이름). 표준 C fopen는 가능한 모든 16 비트 문자열에 매핑되는 가능한 변환이 없기 때문에 모든 NTFS 파일을 열 수 없습니다. Windows 고유의 사용이 _wfopen필요할 수 있습니다. 결론적으로, 처음에 "문자"라는 개념이 없기 때문에 일반적으로 주어진 파일 이름을 구성하는 "문자 수"에 대한 잘 정의 된 개념이 없습니다. 주의 사항.


이것이 순수한 표준 C / C ++만을 사용하여 관용적이고 이식 가능하며 보편적이며 인코딩에 구애받지 않는 프로그램 코어를 작성하는 올바른 방법입니까?

아니요, 적어도 프로그램을 Windows에서 실행하려는 경우 이러한 모든 속성을 충족 할 수있는 방법은 없습니다. Windows에서는 거의 모든 곳에서 C 및 C ++ 표준을 무시하고 독점적으로 작업 wchar_t해야합니다 (반드시 내부적으로는 아니지만 시스템에 대한 모든 인터페이스). 예를 들어 다음으로 시작하면

int main(int argc, char** argv)

명령 줄 인수에 대한 유니 코드 지원이 이미 손실되었습니다. 당신은 작성해야

int wmain(int argc, wchar_t** argv)

대신 GetCommandLineWC 표준에 지정되지 않은 함수를 사용하십시오 .

더 구체적으로,

  • Windows의 모든 유니 코드 가능 프로그램은 명령 줄 인수, 파일 및 콘솔 I / O 또는 파일 및 디렉터리 조작과 같은 작업에 대해 C 및 C ++ 표준을 적극적으로 무시해야합니다. 이것은 확실히 관용적 이지 않습니다 . 대신 Boost.Filesystem 또는 Qt와 같은 Microsoft 확장 또는 래퍼를 사용하십시오.
  • 특히 유니 코드 지원의 경우 이식성 은 달성하기 매우 어렵습니다. 당신은 당신이 알고 있다고 생각하는 모든 것이 잘못되었을 수 있다는 것을 정말로 준비해야합니다. 예를 들어 파일을 여는 데 사용하는 파일 이름이 실제로 사용되는 파일 이름과 다를 수 있으며 겉보기에 다른 두 파일 이름이 동일한 파일을 나타낼 수 있다는 점을 고려해야합니다. 두 개의 파일 ab를 만든 후에 는 단일 파일 c 또는 파일 이름이 OS에 전달한 파일 이름과 다른 두 개의 파일 de로 끝날 수 있습니다 . 외부 래퍼 라이브러리 또는 많은 #ifdefs 가 필요합니다 .
  • 인코딩 불가 지성은 일반적으로 실제로는 작동하지 않으며 특히 이식성이 필요한 경우에는 더욱 그렇습니다. wchar_tWindows에서는 UTF-16 코드 단위이고 charLinux에서는 종종 UTF-8 코드 단위 라는 것을 알아야합니다 . 인코딩 인식이 더 바람직한 목표 인 경우가 많습니다. 작업하는 인코딩을 항상 알고 있는지 확인하거나이를 추상화하는 래퍼 라이브러리를 사용하세요.

I think I have to conclude that it's completely impossible to build a portable Unicode-capable application in C or C++ unless you are willing to use additional libraries and system-specific extensions, and to put lots of effort in it. Unfortunately, most applications already fail at comparatively simple tasks such as "writing Greek characters to the console" or "supporting any filename allowed by the system in a correct manner", and such tasks are only the first tiny steps towards true Unicode support.


I would avoid the wchar_t type because it's platform-dependent (not "serializable" by your definition): UTF-16 on Windows and UTF-32 on most Unix-like systems. Instead, use the char16_t and/or char32_t types from C++0x/C1x. (If you don't have a new compiler, typedef them as uint16_t and uint32_t for now.)

DO define functions to convert between UTF-8, UTF-16, and UTF-32 functions.

DON'T write overloaded narrow/wide versions of every string function like the Windows API did with -A and -W. Pick one preferred encoding to use internally, and stick to it. For things that need a different encoding, convert as necessary.


The problem with wchar_t is that encoding-agnostic text processing is too difficult and should be avoided. If you stick with "pure C" as you say, you can use all of the w* functions like wcscat and friends, but if you want to do anything more sophisticated then you have to dive into the abyss.

Here are some things that much harder with wchar_t than they are if you just pick one of the UTF encodings:

  • Parsing Javascript: Identifers can contain certain characters outside the BMP (and lets assume that you care about this kind of correctness).

  • HTML: How do you turn &#65536; into a string of wchar_t?

  • Text editor: How do you find grapheme cluster boundaries in a wchar_t string?

If I know the encoding of a string, I can examine the characters directly. If I don't know the encoding, I have to hope that whatever I want to do with a string is implemented by a library function somewhere. So the portability of wchar_t is somewhat irrelevant as I don't consider it an especially useful data type.

Your program requirements may differ and wchar_t may work fine for you.


Given that iconv is not "pure standard C/C++", I don't think you are satisfying your own specifications.

There are new codecvt facets coming with char32_t and char16_t so I don't see how you can be wrong as long as you are consistent and pick one char type + encoding if the facets are here.

The facets are described in 22.5 [locale.stdcvt] (from n3242).


I don't understand how this doesn't satisfy at least some of your requirements:

namespace ns {

typedef char32_t char_t;
using std::u32string;

// or use user-defined literal
#define LIT u32

// Communicate with interface0, which wants utf-8

// This type doesn't need to be public at all; I just refactored it.
typedef std::wstring_convert<std::codecvt_utf8<char_T>, char_T> converter0;

inline std::string
to_interface0(string const& s)
{
    return converter0().to_bytes(s);
}

inline string
from_interface0(std::string const& s)
{
    return converter0().from_bytes(s);
}

// Communitate with interface1, which wants utf-16

// Doesn't have to be public either
typedef std::wstring_convert<std::codecvt_utf16<char_T>, char_T> converter1;

inline std::wstring
to_interface0(string const& s)
{
    return converter1().to_bytes(s);
}

inline string
from_interface0(std::wstring const& s)
{
    return converter1().from_bytes(s);
}

} // ns

Then your code can use ns::string, ns::char_t, LIT'A' & LIT"Hello, World!" with reckless abandon, without knowing what's the underlying representation. Then use from_interfaceX(some_string) whenever it's needed. It doesn't affect the global locale or streams either. The helpers can be as clever as needed, e.g. codecvt_utf8 can deal with 'headers', which I assume is Standardese from tricky stuff like the BOM (ditto codecvt_utf16).

In fact I wrote the above to be as short as possible but you'd really want helpers like this:

template<typename... T>
inline ns::string
ns::from_interface0(T&&... t)
{
    return converter0().from_bytes(std::forward<T>(t)...);
}

which give you access to the 3 overloads for each [from|to]_bytes members, accepting things like e.g. const char* or ranges.

참고URL : https://stackoverflow.com/questions/6300804/wchars-encodings-standards-and-portability

반응형