Программирование с использованием широких символов wchar_t

Стандарт ISO C90 ввел тип широких символов wchar_t, тем самым назначив официальный стандарт для широких символов в языке Си. Однако его использование не очень хорошо понимается программистами на Си, и отладка широких символов с помощью отладчика GDB является проблемой, решить которую могут немногие. В результате многие программисты прибегают к использованию массивов символов ASCII, что не очень хорошо; Сегодня локализованный код имеет все большее значение.

Чтобы использовать wchar_t, включите заголовок <wchar.h>. Строковые и символьные литералы необходимо предварять префиксом L, например:

wchar_t widechar = L'\0';  
wchar_t* widestring = L"Hello, world!"; 

Только тогда компилятор C создаст правильные широкие символы. В моей системе wchar_t это четыре байта каждый, в отличие от char, который составляет только один байт.

По факту для широких символов лучше использовать тип int32_t определенный в заголовке stdint.h т.к. было сказано выше не во всех системах wchar_t это четыре байта, а int32_t во всех. Функции для работы с широкими символами прекрасно работают с типом int32_t

Нюансы при работе с функциями

Программирование с использованием устаревших char просто: каждый символ имеет ширину в один байт на каждой платформе, есть только несколько хорошо известных управляющих символов, и если символ пригоден для печати, он занимает ровно один экранный столбец.

Использование широких символов требует, чтобы вы были более осторожны с этими единицами. Например, один символ может занимать более одного столбца экранного пространства, но модификаторы длины в строке формата printf принимают свой размер в байтах. Решений несколько. Одно из них использование функций wcslen и wcswidth:

size_t wcslen (const wchar_t *s);  
int wcswidth (const wchar_t *s, size_t n); 

Используйте функции wcslen при выделении памяти для широких символов и используйте wcswidth для выравнивания текста при печати.

Вторым решением является использование функций специально предназначенных для работы с широкими символами о них далее.

Печать

Спецификатор преобразования строки формата printf для массивов широких символов выглядит так %ls. Вы не должны использовать %s т.к. это определено только в Единой спецификации UNIX версии 2, но не в стандарте ISO C.

Вы можете использовать printf для вывода строк широких символов, но для этого больше подходит функция wprintf, потому что она изначально обрабатывает широкие символы. Например, в функции wprintf единица для модификаторов длины - это "широкие символы" - в отличие от байтов с printf. Другие функции из семейства printf также имеют эквиваленты для широких символов.

При использовании wprintf или fwprintf выходной поток должен быть в режиме широких символов. Чтобы переключить выходной поток, используйте fwide. Например, чтобы переключить стандартный вывод в режим широких символов:

/* передача fwide вторым аргументом 0 означает просто запрос режима */
if (fwide(stdout, 0) == 0) { 
    /* stdout пока не имеет установленного режима. Попытаемся установить режим широких символов */  
    if (fwide(stdout, 1) <= 0) /* значение больше 0 переключает поток в режим широких символов */
        printf("could not switch to wide char mode!\n");  
    else
        wprintf(L"switched to wide char mode.\n");  
}

Как только режим установлен, его нельзя изменить, кроме как с помощью вызова freopen на потоке, поэтому не забудьте заранее установить режим - особенно для stdout.

Применение строковых функций

Каждый программист на С работал с функциями, определенными в заголовке string.h. Никогда не используйте эти функции для строк широких символов!

Каждая из функций, определенных в string.h имеет широкий символьный эквивалент - просто замените префикс str на wcs. Вы можете найти все эти и другие функции (например, адаптеры к функциям mem*) на странице руководства:

$ man wchar.h

Широкие символы и многобайтовые строки

Широкие символы это конечно классно, но не экономно. Широкие символы это всегда фиксированный размер, даже если фактически символ умещается в один байт. Для пущей экономии придумали кодировки с переменным кол-вом байтов аля UTF-8 которые используют память более экономно, за счет более хитрого кодирования (там в первом октете символа кодируется длина этого самого символа в октетах, а последующих октетах в общем то идет полезная нагрузка) тут наглядно объяснено: How About UNICODE and UTF-8 Так вот такие кодировки с переменным количеством байт на символ кодируются Многобайтовыми строками.

Многобайтовые строки - это классический способ кодирования алфавитов, которые не вписываются в классическую карту из 256 символов, например, китайскую. Есть две функции для преобразования многобайтовых строк в строки широких символов и наоборот:

#include <stdlib.h>`

size_t mbstowcs (wchar_t * dest, const char * src, size_t n);  
size_t wcstombs (char * dest, const wchar_t * src, size_t n);  

Возвращаемое значение этих функций - количество успешно преобразованных байтов. Обратите внимание, что преобразование зависит от значения переменной среды LC_CTYPE; это связано с тем, что значения больше 127 представляют разные символы в зависимости от настроек локали.

GNU Gettext использует только многобайтовые строки и это вряд ли изменится в ближайшем будущем. Это потому, что широкие символы занимают много места над головой. Следовательно, вам придется применить mbstowcs к строкам, которые получает gettext ().

Отображение широких символов ASCII в GDB

GNU Debugger не знает , как интерпретировать wchar_t указатель. Это связано с тем, что разработчики GDB не уверены, как правильно обрабатывать различные наборы символов, используемые системой, GDB и отлаживаемой программой. Другая проблема заключается в возможности печати символов: то, будет ли печататься символ, зависит от используемого шрифта и его семантики (например, некоторые символы являются управляющими символами).

Хотя эти две проблемы, безусловно, являются насущними, разработчикам не удалось добавить в GDB базовую поддержку для печати простых символов ASCII, что требует исправления.

Я написал скрипт, чтобы сделать это возможным, определив новую команду wchar_print (ее должно хватить для ввода wc). Вы можете скачать его и использовать его несколькими способами:

  • Поместите его содержимое в ваш файл .gdbinit
  • Включите его в свой файл .gdbinit с помощью sourceкоманды
  • Заставьте GDB прочитать его при запуске с gdb -x wchar.gdb
  • Заставьте GDB прочитать это в сеансе: (gdb) source wchar.gdb

Вы можете вызвать функцию с указателем на wchar_t:

(gdb) wchar_print widestring  
"Hello, world!"  

Обратите внимание, что wchar_print будет давать неправильные результаты для широких символов со значением больше 127.