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