Следующее не может рассматриваться как вопрос SO; если это выходит за рамки, пожалуйста, не стесняйтесь сказать мне, чтобы я ушел. В основном вопрос заключается в следующем: «Правильно ли я понимаю стандарт C и правильно ли это?»
Я хотел бы попросить разъяснений, подтверждения и исправлений в моем понимании обработки символов в C (и таким образом, C ++ и C ++ 0x). Прежде всего, важное наблюдение:
Переносимость и сериализация - ортогональные концепции.
Переносимые вещи - это такие вещи, как C, unsigned int
, wchar_t
. Сериализуемые вещи - это такие вещи, как uint32_t
или UTF-8. «Переносимость» означает, что вы можете перекомпилировать один и тот же исходный код и получить рабочий результат на каждой поддерживаемой платформе, но двоичное представление может быть совершенно другим ( или даже не существует, например TCP-over-carrier pigeon). Сериализуемые объекты на других r рука всегда имеет такое же представление, например файл PNG, который я могу прочитать на рабочем столе Windows, на телефоне или на зубной щетке. Переносимые вещи - это внутренние, сериализуемые вещи имеют дело с вводом-выводом. Переносимые вещи безопасны по типу, сериализуемые вещи нуждаются в типизации.
Когда дело доходит до обработки символов в 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 .
Итак, я должен всегда хранить свои строки внутри в независимой от кодировки 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 ---+
Практически это означает, что я бы написал две стандартные оболочки для m y точка входа в программу, например для 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-drop вместе с GetCommandLineW
+ CommandLineToArgvW
работает (возможно, должна быть отдельная оболочка для Windows).
Файловые системы: Файловые системы, похоже, не имеют никакого понятия о кодировке и просто принимают любую строку с завершающим нулем в качестве имени файла. Большинство систем принимают байтовые строки, но Windows / NTFS принимает 16-битные строки. Вы должны проявлять осторожность при обнаружении существующих файлов и при обработке этих данных (например, последовательности char16_t
, которые не составляют допустимый UTF16 (например, голые суррогаты), являются допустимыми именами файлов NTFS). Стандартный C fopen
не может открывать все файлы NTFS, так как не существует возможного преобразования, которое отображало бы все возможные 16-битные строки. Может потребоваться использование специфичного для Windows _wfopen
. Как следствие, в целом не существует четко определенного понятия «сколько символов» содержится в данном имени файла, так как в первую очередь отсутствует понятие «символ». Caveat emptor.