Следующие не могут квалифицироваться как вопрос SO; если это вне пределов, пожалуйста, не стесняйтесь сказать мне уйти. Вопрос здесь в основном: "Правильно ли я понимаю стандарт C и правильно ли это происходит?"
Я хотел бы попросить разъяснения, подтверждения и исправления в моем понимании обработки символов в C (и, следовательно, С++ и С++ 0x). Во-первых, важное замечание:
Переносимость и сериализация - это ортогональные понятия.
Переносимые вещи - это такие вещи, как C, unsigned int
, wchar_t
. Сериализуемые вещи - это такие вещи, как uint32_t
или UTF-8. "Portable" означает, что вы можете перекомпилировать один и тот же источник и получить рабочий результат на каждой поддерживаемой платформе, но двоичное представление может быть совершенно другим (или даже не существует, например, TCP-over-carrier pigeon). Сериализуемые вещи, с другой стороны, всегда имеют одинаковое представление, например. файл PNG, который я могу прочитать на рабочем столе Windows, на моем телефоне или на моей зубной щетке. Переносимые вещи - это внутренние, сериализуемые вещи, связанные с I/O. Переносимые вещи являются типичными, сериализуемыми вещами, требующими типа punning. </преамбула >
Когда дело доходит до обработки символов в C, есть две группы вещей, связанные соответственно с переносимостью и сериализацией:
-
wchar_t
,setlocale()
,mbsrtowcs()
/wcsrtombs()
: Стандарт C ничего не говорит о "кодировках" ; на самом деле, он полностью агностик для любых свойств текста или кодирования. Он только говорит: "Ваша точка входаmain(int, char**)
, вы получаете типwchar_t
, который может содержать все ваши системные символы, вы получаете функции для считывания входных char -последовательностей и превращения их в работоспособные wstrings и наоборот. -
iconv()
и UTF-8,16,32: функция/библиотека для перекодирования между четко определенными, определенными фиксированными кодировками. Все кодировки, обрабатываемые iconv, повсеместно понятны и согласованы, за одним исключением.
Мост между переносимым, кодирующим-агностическим миром C с его переносным символьным типом wchar_t
и детерминированным внешним миром - это преобразование iconv между WCHAR-T и UTF.
Итак, должен ли я всегда хранить свои строки внутри кодировки-agnostic 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 ---+
Практически это означает, что я бы написал две обертки для котельной пластины для моей точки входа в программу, например. для С++:
// 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/С++, а также хорошо определенный интерфейс ввода-вывода для UTF с помощью iconv? (Обратите внимание, что такие проблемы, как нормализация Unicode или диакритическая замена, находятся за пределами области действия, и только после того, как вы решите, что на самом деле хотите использовать Unicode (в отличие от любой другой системы кодирования, которую вы можете представить), пора использовать эти особенности, например, используя специальную библиотеку как libicu.)
Обновление
Следуя многим очень приятным комментариям, я хотел бы добавить несколько замечаний:
-
Если ваше приложение явно хочет иметь дело с текстом Unicode, вы должны сделать
iconv
-конверсионную часть ядра и использоватьuint32_t
/char32_t
-строки внутри с UCS-4. -
Windows: при использовании широких строк, как правило, отлично, кажется, что взаимодействие с консолью (любая консоль, если на то пошло) ограничено, так как, похоже, не поддерживается какая-либо разумная многобайтовая консольная кодировка и
mbstowcs
по существу бесполезен (кроме тривиального расширения). Получение широкоформатных аргументов из, скажем, проводника-обозревателя вместе сGetCommandLineW
+CommandLineToArgvW
работает (возможно, для Windows должна быть отдельная оболочка). -
Файловые системы: Файловые системы, похоже, не имеют понятия кодирования и просто берут любую строку с нулевым завершением в качестве имени файла. Большинство систем берут байтовые строки, но Windows/NTFS принимает 16-битные строки. Вы должны следить за тем, какие файлы существуют и когда обрабатывать эти данные (например,
char16_t
последовательности, которые не являются действительными UTF16 (например, голые суррогаты), являются действительными именами файлов NTFS). Стандарт Cfopen
не может открыть все файлы NTFS, поскольку нет возможного преобразования, которое будет отображаться для всех возможных 16-разрядных строк. Может потребоваться использование_wfopen
для Windows. В качестве следствия, как правило, нет четкого представления о том, "сколько символов" содержит заданное имя файла, поскольку вначале нет понятия "символ". Предостережение emptor.