web-dev-qa-db-ja.com

WChars、エンコーディング、標準、移植性

以下は、SOの質問に該当しない場合があります。範囲外の場合は、遠慮なく遠慮なく教えてください。ここでの質問は、「C標準を正しく理解していますか?これは物事を進める正しい方法ですか?」

C(したがってC++およびC++ 0x)での文字処理の理解について、明確化、確認、および修正をお願いしたいと思います。まず、重要な観察:

移植性とシリアライゼーションは直交する概念です。

移植可能なものは、C、_unsigned int_、_wchar_t_などです。シリアライズ可能なものは、_uint32_t_やUTF-8などです。 「移植可能」とは、同じソースを再コンパイルして、サポートされているすべてのプラットフォームで動作する結果を得ることができることを意味しますが、バイナリ表現は完全に異なる場合があります(または、TCP overキャリアピジョンなど存在しない場合もあります)。一方、シリアライズ可能なものは常にsame表現を持ちます。 Windowsデスクトップ、携帯電話、または歯ブラシで読むことができるPNGファイル。ポータブルなものは内部であり、I/Oを処理するシリアライズ可能なものです。移植可能なものはタイプセーフであり、シリアライズ可能なものは型パンニングを必要とします。 </ preamble>

Cでの文字処理に関しては、移植性と直列化にそれぞれ関連する2つのグループがあります。

  • _wchar_t_、setlocale()mbsrtowcs()/wcsrtombs()C標準では「エンコーディング」について何も述べられていません;実際、それはテキストやエンコーディングプロパティに完全に依存しません。 「エントリポイントはmain(int, char**)です。システムのすべての文字を保持できる型_wchar_t_を取得します。入力文字シーケンスを読み取り、実行可能なwstringに変換する関数を取得します逆。

  • iconv()およびUTF-8,16,32:明確に定義された、固定された固定エンコーディング間でトランスコードする関数/ライブラリ。 iconvによって処理されるすべてのエンコーディングは、1つの例外を除いて、広く理解され、合意されています。

_wchar_t_ポータブル文字型を使用した、Cのポータブルでエンコードに依存しない世界と確定的な外部の世界との間の架け橋はWCHAR-TとUTF間のiconv変換です。

したがって、常に文字列をエンコードに依存しないwstringに内部的に保存し、wcsrtombs()を介してCRTとインターフェイスし、シリアル化に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 ---+
_

実際には、プログラムのエントリポイント用に2つのボイラープレートラッパーを記述します。 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);

/* ... */
_

これは、純粋な標準C/C++とiconvを使用したUTFへの明確に定義されたI/Oインターフェースのみを使用して、エンコーディングにとらわれない、慣用的で移植性のある慣用的なプログラムコアを作成する正しい方法ですか? (Unicodeの正規化や分音記号の置換などの問題は範囲外です。実際に必要であると判断した後でのみnicode(ファンシーかもしれない他のコーディングシステムとは対照的に)、それらに対処する時が来ました詳細、例えばlibicuのような専用ライブラリを使用する。)

更新

多くの非常に素晴らしいコメントに続いて、私はいくつかの観察を追加したいと思います:

  • アプリケーションが明示的にUnicodeテキストを処理する場合は、コアのiconv- conversion部分を作成し、UCS-4で内部的に_uint32_t_/_char32_t_- stringsを使用する必要があります。

  • Windows:ワイド文字列の使用は一般的には問題ありませんが、コンソール(つまり、任意のコンソール)との対話は制限されているようです。賢明なマルチバイトコンソールエンコーディングはサポートされていないようで、mbstowcsは本質的に役に立たない(ささいな拡大以外)。エクスプローラードロップからワイド文字列引数を受け取ると、GetCommandLineW + CommandLineToArgvWが機能します(おそらく、Windows用の個別のラッパーがあるはずです)。

  • ファイルシステム:ファイルシステムはエンコーディングの概念を持たないようで、単にnullで終了する文字列をファイル名として受け取ります。ほとんどのシステムはバイト文字列を取りますが、Windows/NTFSは16ビット文字列を取ります。存在するファイルを検出するとき、およびそのデータを処理するとき(たとえば、有効なUTF16を構成しない_char16_t_シーケンス(ネイキッドサロゲートなど)は有効なNTFSファイル名です)に注意する必要があります。標準Cのfopenでは、すべてのNTFSファイルを開くことができません。これは、考えられるすべての16ビット文字列にマップされる変換が存在しないためです。 Windows固有の__wfopen_の使用が必要になる場合があります。当然のこととして、そもそも「文字」の概念がないため、特定のファイル名を構成する「何文字」という明確な概念はありません。買い手責任負担。

61
Kerrek SB

これは、純粋な標準C/C++のみを使用して、慣用的で、移植性があり、ユニバーサルで、エンコードに依存しないプログラムコアを作成する正しい方法ですか?

いいえ、少なくともプログラムをWindowsで実行したい場合は、これらのすべてのプロパティを満たす方法はまったくありません。 Windowsでは、ほとんどすべての場所でCおよびC++標準を無視し、wchar_tだけで作業する必要があります(必ずしも内部ではなく、システムへのすべてのインターフェースで)。たとえば、

int main(int argc, char** argv)

コマンドライン引数のUnicodeサポートはすでに失われています。あなたは書く必要があります

int wmain(int argc, wchar_t** argv)

代わりに、またはGetCommandLineW関数を使用しますが、C標準ではいずれも指定されていません。

すなわち、

  • windows上のすべてのUnicode対応プログラムは、コマンドライン引数、ファイルとコンソールのI/O、またはファイルとディレクトリの操作などについて、CおよびC++標準を積極的に無視する必要があります。これは確かに慣用的ではありません。代わりに、Boost.FilesystemやQtなどのMicrosoft拡張機能またはラッパーを使用してください。
  • Portabilityは、特にUnicodeサポートの場合、実現が非常に困難です。あなたは本当にあなたがあなたが知っていると思うすべてが間違っている可能性があることを覚悟しなければなりません。たとえば、ファイルを開くために使用するファイル名は、実際に使用されるファイル名とは異なる場合があり、一見異なる2つのファイル名が同じファイルを表す場合があることを考慮する必要があります。 2つのファイルaおよびbを作成した後、1つのファイルcまたは2つのファイルdになる可能性があります。 =およびeであり、そのファイル名はOSに渡したファイル名とは異なります。外部ラッパーライブラリまたは多数の#ifdefsが必要です。
  • エンコーディングの不可知性は、特に移植可能にしたい場合は、通常、実際には機能しません。 wchar_tはWindowsではUTF-16コード単位であり、charはLinuxでは多くの場合(ボットとは限りません)UTF-8コード単位であることを知っておく必要があります。多くの場合、エンコーディングを意識することがより望ましい目標です。どのエンコーディングを使用するかを常に把握しておくか、それらを抽象化するラッパーライブラリを使用してください。

追加のライブラリーとシステム固有の拡張機能を使用し、それに多大な労力を費やすことを望まない限り、CまたはC++でポータブルUnicode対応アプリケーションを構築することは完全に不可能であると私は結論づけなければならないと思います。残念ながら、ほとんどのアプリケーションは「ギリシャ語の文字をコンソールに書き込む」、「システムで許可されているファイル名を正しくサポートする」などの比較的単純なタスクで失敗し、そのようなタスクは真のUnicodeサポートに向けた最初の小さなステップにすぎません。

21
Philipp

wchar_tタイプはプラットフォームに依存しているため(定義によって「シリアライズ可能」ではない)、WindowsではUTF-16、ほとんどのUnixライクなシステムではUTF-32であるため、私は避けます。代わりに、C++ 0x/C1xのchar16_tおよびchar32_tタイプを使用してください。 (新しいコンパイラがない場合は、現時点ではuint16_tおよびuint32_tとしてtypedefしてください。)

[〜#〜] do [〜#〜] UTF-8、UTF-16、およびUTF-32関数間で変換する関数を定義します。

DO N'T Windows APIが-Aおよび-Wで行ったようなevery文字列関数のオーバーロードされたナロー/ワイドバージョンを書き込みます。内部で使用するone優先エンコーディングを選択し、それに固執します。別のエンコーディングが必要な場合は、必要に応じて変換してください。

9
dan04

wchar_tの問題は、エンコードに依存しないテキスト処理が非常に難しいため、回避する必要があることです。あなたが言うように「純粋なC」に固執する場合、wcscatおよび友人のようなw*関数のすべてを使用できますが、より高度なことをしたい場合は、深淵。

以下は、UTFエンコーディングの1つを選択する場合よりも、wchar_tを使用する場合の方がはるかに難しいものです。

  • Javascriptの解析:識別子には、BMPの外に特定の文字を含めることができます(この種の正確性に関心があると想定してください)。

  • HTML:どのように&#65536;wchar_tの文字列に変換しますか?

  • テキストエディター:wchar_t文字列で書記素クラスターの境界をどのように見つけますか?

文字列のエンコーディングがわかっていれば、文字を直接調べることができます。エンコーディングがわからない場合は、文字列を使用して実行する処理がライブラリ関数によってどこかに実装されていることを期待する必要があります。したがって、wchar_tの移植性は、特に便利データ型であるとは考えていないため、あまり関係ありません。

プログラム要件は異なる場合があり、wchar_tが適切に機能する場合があります。

8
Dietrich Epp

iconvが「純粋な標準C/C++」ではないことを考えると、自分の仕様を満たしているとは思えません。

_char32_t_と_char16_t_に新しいcodecvtファセットが追加されたので、一貫性があり、ファセットの場合は1つの文字タイプ+エンコーディングを選択する限り、どのように間違っているかはわかりませんあります。

ファセットについては、22.5 [locale.stdcvt](n3242以降)で説明されています。


これがどのようにして少なくともいくつかの要件を満たさないのか理解できません。

_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
_

その後、コードは_ns::string_、_ns::char_t_、_LIT'A'_&_LIT"Hello, World!"_を、根底にある表現が何であるかを知らずに無謀に放棄して使用できます。その後、必要に応じてfrom_interfaceX(some_string)を使用します。グローバルロケールやストリームにも影響しません。ヘルパーは、必要に応じて賢くすることができます。 _codecvt_utf8_は、「ヘッダー」を処理できます。これは、BOM(同種の_codecvt_utf16_)などのトリッキーなものからのStandardeseだと思います。

実際、私は上記を可能な限り短くするために書きましたが、あなたは本当にこのようなヘルパーが欲しいでしょう:

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

これにより、各_[from|to]_bytes_メンバーの3つのオーバーロードにアクセスできるようになり、たとえば、 _const char*_または範囲。

6
Luc Danton