Discussion:
wchar_t и кодировки
(слишком старое сообщение для ответа)
Vadim Guchenko
2009-08-13 10:08:15 UTC
Permalink
Hello, All!

Тип wchar_t в стандартах определен так, что он должен вмещать код любого
символа любой кодировки, поддерживаемой системой, однако ничего не
сказано о формате хранения данных в этом типе. С одной стороны
утверждается, что это внутренний тип данных языка C, и формат хранения
данных в нем определяется компилятором, а для приложения этот формат не
имеет значения. И действительно, в разных осях этот тип имеет даже
разный размер (в винде - 2 байта, в юниксах - 4). Причем gcc имеет два
ключа, один из которых влияет на размер типа wchar_t (-fshort-wchar), а
второй, как я понял, позволяет задать кодировку символов внутри типа
wchar_t (-fwide-exec-charset). И хотя по умолчанию эта кодировка UTF-32
или UTF-16 в зависимости от выбранного размера wchar_t, теоретически там
можно указать любую кодировку, даже не связанную с юникодом. С другой
стороны стандартная библиотека C содержит множество функций, принимающих
и возвращающих тип wchar_t, в том числе преобразующих wide character в
multibyte character и обратно. А для такого преобразования функции
должны знать не только кодировку для multibyte characters, которая
определяется по текущей локали, но и кодировку символов внутри типа
wchar_t.

Собственно вопрос: в какой кодировке должны быть символы в wchar_t для
корректной работы стандартных библиотечных функций FreeBSD и где об этом
написано? Более конкретно: в какой кодировке нужно передать символ
wchar_t в функцию wctomb(), чтобы на выходе получить этот же символ в
кодировке текущей локали?

--
Best regards, Vadim Guchenko [yhw at relost dot net].
Artem Chuprina
2009-08-13 11:56:46 UTC
Permalink
Vadim Guchenko -> All @ Thu, 13 Aug 2009 10:08:15 +0000 (UTC):

VG> Тип wchar_t в стандартах определен так, что он должен вмещать код любого
VG> символа любой кодировки, поддерживаемой системой, однако ничего не
VG> сказано о формате хранения данных в этом типе. С одной стороны
VG> утверждается, что это внутренний тип данных языка C, и формат хранения
VG> данных в нем определяется компилятором, а для приложения этот формат не
VG> имеет значения. И действительно, в разных осях этот тип имеет даже
VG> разный размер (в винде - 2 байта, в юниксах - 4). Причем gcc имеет два
VG> ключа, один из которых влияет на размер типа wchar_t (-fshort-wchar), а
VG> второй, как я понял, позволяет задать кодировку символов внутри типа
VG> wchar_t (-fwide-exec-charset). И хотя по умолчанию эта кодировка UTF-32
VG> или UTF-16 в зависимости от выбранного размера wchar_t, теоретически там
VG> можно указать любую кодировку, даже не связанную с юникодом. С другой
VG> стороны стандартная библиотека C содержит множество функций, принимающих
VG> и возвращающих тип wchar_t, в том числе преобразующих wide character в
VG> multibyte character и обратно. А для такого преобразования функции
VG> должны знать не только кодировку для multibyte characters, которая
VG> определяется по текущей локали, но и кодировку символов внутри типа
VG> wchar_t.

VG> Собственно вопрос: в какой кодировке должны быть символы в wchar_t для
VG> корректной работы стандартных библиотечных функций FreeBSD и где об этом
VG> написано? Более конкретно: в какой кодировке нужно передать символ
VG> wchar_t в функцию wctomb(), чтобы на выходе получить этот же символ в
VG> кодировке текущей локали?

Чтобы все аккуратно работало, надо получить этот символ из mbtowc(). Hа
самом деле по умолчанию во фрюхе ни разу не UTF-32 :-) Как показала практика.
--
У кошки четыре ноги: ввод, вывод, земля и питание.
Vadim Guchenko
2009-08-13 12:41:58 UTC
Permalink
Hello, Artem!
You wrote to Vadim Guchenko on Thu, 13 Aug 2009 11:56:46 +0000 (UTC):

VG>> Собственно вопрос: в какой кодировке должны быть символы в
VG>> wchar_t для корректной работы стандартных библиотечных функций
VG>> FreeBSD и где об этом написано? Более конкретно: в какой
VG>> кодировке нужно передать символ wchar_t в функцию wctomb(),
VG>> чтобы на выходе получить этот же символ в кодировке текущей
VG>> локали?
AC> Чтобы все аккуратно работало, надо получить этот символ из
AC> mbtowc().

А как быть со строковыми литералами, заданными в программе через L"..."?
Компилятор их переводит во внутреннее представление wchar_t тоже через
вызов mbtowc() или все же напрямую через iconv, зная исходную и
результирующую кодировку? Hаличие у gcc ключа -fwide-exec-charset и его
описание наталкивает на мысль, что все-таки второе. А значит кодировка
wchar_t, указанная через этот ключ, должна совпадать с той кодировкой,
которую ожидают на входе библиотечные функции в конкретной системе. А
для этого ее нужно знать. И если не самой программе, то человеку,
который будет ее компилировать.

--
Best regards, Vadim Guchenko [yhw at relost dot net].
Artem Chuprina
2009-08-14 08:47:06 UTC
Permalink
Vadim Guchenko -> Artem Chuprina @ Thu, 13 Aug 2009 12:41:58 +0000 (UTC):

VG>>> Собственно вопрос: в какой кодировке должны быть символы в
VG>>> wchar_t для корректной работы стандартных библиотечных функций
VG>>> FreeBSD и где об этом написано? Более конкретно: в какой
VG>>> кодировке нужно передать символ wchar_t в функцию wctomb(),
VG>>> чтобы на выходе получить этот же символ в кодировке текущей
VG>>> локали?
AC>> Чтобы все аккуратно работало, надо получить этот символ из
AC>> mbtowc().

VG> А как быть со строковыми литералами, заданными в программе через L"..."?
VG> Компилятор их переводит во внутреннее представление wchar_t тоже через
VG> вызов mbtowc() или все же напрямую через iconv, зная исходную и
VG> результирующую кодировку? Hаличие у gcc ключа -fwide-exec-charset и его
VG> описание наталкивает на мысль, что все-таки второе. А значит кодировка
VG> wchar_t, указанная через этот ключ, должна совпадать с той кодировкой,
VG> которую ожидают на входе библиотечные функции в конкретной системе. А
VG> для этого ее нужно знать. И если не самой программе, то человеку,
VG> который будет ее компилировать.

У меня в info gcc рассказывают про -fwide-exec-charset, что CHARSET can
be any encoding supported by the system's `iconv' library routine.
Стало быть, iconv.

Документировано мутно, но что-то мне подсказывает, что конструкция там
устроена так: при чтении файла константы переводятся из -finput-charset
в -fwide-exec-charset или -fexec-charset в зависимости от того, было там
L или нет. Потом результат скармливается движку компиляции. Который
переводит "широкие" константы в системно-зависимый wchar_t, про который
надо знать сборщику компилятора, а не сборщику программы.

Hо это мои догадки, а не факт. Вообще тут надо проверить, но сколь я
помню, на практике интерпретация значения wchar_t вообще может зависеть
от текущей локали (т.е. mbtowc(); setlocale(); wctomb() может не давать
перекодировку). А может и не зависеть. В линуксе не зависит, а в
FreeBSD зависит. Так что что там на самом делается между
wide-exec-charset и wchar_t, покрыто мраком.
--
Пользователь юникса перестаёт быть пользователем юникса если после его
пользования пользованный юникс перестаёт быть юниксом. (с)
Vadim Guchenko
2009-09-14 15:36:38 UTC
Permalink
Hello, Artem!
You wrote to Vadim Guchenko on Fri, 14 Aug 2009 08:47:06 +0000 (UTC):

Hаписанное ниже является результатом изучения различной информации в
сети о кодировках и их поддержке в языке C, стандарта C99 и Rationale к
нему, исходников gcc, libiconv и libc.

Hачать с того, что gcc может работать как с libiconv, так и без него. В
последнем случае gcc использует для различных перекодировок внутренние
функции, которые ограничиваются только поддержкой юникода (UTF-8 <->
UTF-16/UTF-32). Этого достаточно для запуска gcc с дефолтными
настройками кодировок, однако никакие 8-битные кодировки самим gcc не
поддерживаются. В таком виде gcc стоит на свежеустановленной FreeBSD
7.1. Поэтому, чтобы запустить gcc с ключом -finput-charset=KOI8-R,
пришлось переустановить его из портов после того, как был установлен
libiconv. По такому случаю была поставлена наиболее свежая версия gcc
version 4.3.5 20090823. Была выбрана именно эта, а не самая старшая
версия, в надежде, что пятая ревизия менее глючная, чем первая или
вторая. Дальше речь пойдет именно об этой версии gcc.

Внутри gcc есть 4 кодировки:

1. input charset - кодировка, в которой написаны исходники компилируемой
программы. Может задаваться ключом -finput-charset. По умолчанию UTF-8.
2. source charser - внутренняя кодировка, в которую gcc переводит все
исходные тексты перед началом их анализа. Если не принимать во внимание,
что где-то существует такая экзотическая кодировка, как EBCDIC, то можно
считать, что в качестве source charset gcc всегда использует UTF-8.
3. narrow charset - кодировка, в которую gcc переводит все узкие символы
и строки (обычный тип char) и в таком виде сохраняет их в бинарнике.
Может задаваться ключом -fexec-charset. По умолчанию UTF-8.
4. wide charset - кодировка, в которую gcc переводит все широкие символы
и строки (тип wchar_t) и в таком виде сохраняет их в бинарнике. Может
задаваться ключом -fwide-exec-charset. Для юниксов по умолчанию UTF-32LE
или UTF-32BE в зависимости от системного порядка байт (хотя на самом
деле там UCS-4, но это неважно). Кодировка должна быть обязательно с
фиксированным количеством байтов на символ, равным sizeof(wchar_t).

Таким образом, при компиляции исходник сначала целиком переводится из
input charset в source charset, а уже в процессе разбора узкие символы и
строки переводятся из source charset в narrow charset, а широкие - из
source charset в wide charset. Все перекодировки делаются либо
встроенными в gcc функциями, либо через iconv(). Библиотечные функции
семейства mbtowc() не вызываются вообще.

В целом картина соответствует стандарту C99. Правда стандарт явно
определяет лишь две кодировки - source и execution. Две кодировки нужны
в соответствии с идеологией, что программа может компилироваться в одной
системе (соответственно ее исходники написаны в родной для этой системы
source charset), а запускаться в другой системе, где родной кодировкой
является execution charset. Hет жесткой привязки к какой-то существующей
кодировке (например, US-ASCII), однако для source и execution charset
стандарт требует, чтобы каждый символ из определенного набора, куда
включаются символы латинского алфавита, цифры, знаки и некоторые
управляющие символы, при кодировании помещался ровно в один байт, коды
символов от '0' до '9' шли по порядку, а нулевой байт должен иметь
специальное значение - конец строки. Я не очень понял, зачем нужны такие
требования, но вероятно это сделано для совместимости со старым кодом,
работа которого основана на таких допущениях. Остальные символы могут
быть однобайтовыми или многобайтовыми. Таким условиям удовлетворяют
кодировки UTF-8, ASCII, KOI8-R, но не удовлетворяют UTF-16 и UTF-32.

gcc к этой схеме добавляет третью кодировку input charset, на которую
теоретически не накладывается никаких ограничений (об этой кодировке
неявным образом упоминается в описании первой стадии компиляции
программы в C99). А C99 execution charset в gcc разбивается на две
кодировки - narrow и wide. Предполагается, что эти кодировки
соответствует одному и тому же набору символов, при этом библиотечные
функции mbtowc() и wctomb() должны корректно преобразовывать символы из
одной кодировки в другую и обратно.

Итак, тестовая конфигурация:

s1# uname -srm
FreeBSD 7.1-RELEASE i386
s1# locale
LANG=ru_RU.KOI8-R
LC_CTYPE="ru_RU.KOI8-R"
LC_COLLATE="ru_RU.KOI8-R"
LC_TIME="ru_RU.KOI8-R"
LC_NUMERIC="ru_RU.KOI8-R"
LC_MONETARY="ru_RU.KOI8-R"
LC_MESSAGES="ru_RU.KOI8-R"
LC_ALL=
s1# gcc43 -v
Using built-in specs.
Target: i386-portbld-freebsd7.1
Configured with:
./../gcc-4.3-20090823/configure --disable-nls --with-system-zlib --with-libicon
v-prefix=/usr/local --with-gmp=/usr/local --program-suffix=43 --libdir=/usr/local/lib/gcc43
--li
bexecdir=/usr/local/libexec/gcc43 --with-gxx-include-dir=/usr/local/lib/gcc43/include/c++/
--dis
able-libgcj --prefix=/usr/local --mandir=/usr/local/man --infodir=/usr/local/info/gcc43
--build=
i386-portbld-freebsd7.1
Thread model: posix
gcc version 4.3.5 20090823 (prerelease) (GCC)
s1# ldd /usr/local/bin/gcc43
/usr/local/bin/gcc43:
libiconv.so.3 => /usr/local/lib/libiconv.so.3 (0x280a9000)
libc.so.7 => /lib/libc.so.7 (0x2819e000)

Сначала проверяем работу с узкими строками. Простая программа test.c в
KOI8-R:

#include <stdio.h>
int main() {
printf("Привет!\n");
}

s1# gcc43 test.c -o test && ./test
Привет!

Работает. А если так:

s1# gcc43 test.c -o test -finput-charset=KOI8-R && ./test
п?я─п╦п╡п╣я┌!

Кракозябры. Казалось бы, парадокс. В первом случае кодировка исходника
явно не указана, но согласно ману она должна браться из локали, а там
стоит KOI8-R:

-finput-charset=charset
Set the input character set, used for translation from the charac-
ter set of the input file to the source character set used by GCC.
If the locale does not specify, or GCC cannot get this information
from the locale, the default is UTF-8. This can be overridden by
either the locale or this command line option. Currently the com-
mand line option takes precedence if there's a conflict. charset
can be any encoding supported by the system's "iconv" library rou-
tine.

Однако в исходниках gcc находим такое:

/* Decide on the default encoding to assume for input files. */
const char *
_cpp_default_encoding (void)
{
const char *current_encoding = NULL;

/* We disable this because the default codeset is 7-bit ASCII on
most platforms, and this causes conversion failures on every
file in GCC that happens to have one of the upper 128 characters
in it -- most likely, as part of the name of a contributor.
We should definitely recognize in-band markers of file encoding,
like:
- the appropriate Unicode byte-order mark (FE FF) to recognize
UTF16 and UCS4 (in both big-endian and little-endian flavors)
and UTF8
- a "#i", "#d", "/ *", "//", " #p" or "#p" (for #pragma) to
distinguish ASCII and EBCDIC.
- now we can parse something like "#pragma GCC encoding <xyz>
on the first line, or even Emacs/VIM's mode line tags (there's
a problem here in that VIM uses the last line, and Emacs has
its more elaborate "local variables" convention).
- investigate whether Java has another common convention, which
would be friendly to support.
(Zack Weinberg and Paolo Bonzini, May 20th 2004) */
#if defined (HAVE_LOCALE_H) && defined (HAVE_LANGINFO_CODESET) && 0
setlocale (LC_CTYPE, "");
current_encoding = nl_langinfo (CODESET);
#endif
if (current_encoding == NULL || *current_encoding == '\0')
current_encoding = SOURCE_CHARSET;

return current_encoding;
}

Т.е. ман врет и определение кодировки исходников по текущей локали не
работает этак с 2004 года, если верить комментарию. Поэтому в первом
примере в качестве input charset была выбрана UTF-8. Почему тогда первый
пример вообще работал? Это объясняется тем, что если gcc нужно что-то
перекодировать и исходная кодировка совпадает с кодировкой назначения,
то он не только не вызывает перекодировщик, но и не проверяет символы на
соответствие исходной кодировке. Т.е. строка передается на выход как
есть. Поэтому текст в KOI8-R прозрачно проходит через конвейер input
charset (UTF-8) -> source charset (UTF-8) -> narrow charset (UTF-8).

Во втором примере текст прошел через конвейер KOI8-R -> UTF-8 -> UTF-8,
поэтому на выходе получился правильный UTF-8, который в локали KOI8-R
выводится кракозябрами. Если же написать так:

s1# gcc43 test.c -o test -fexec-charset=KOI8-R && ./test
test.c:3:12: error: converting to execution character set: Illegal byte
sequence

то это даже не компилируется, т.к. при прохождении текста через конвейер
UTF-8 -> UTF-8 -> KOI8-R компилятор выясняет, что на входе на самом деле
никакой не UTF-8.

Hаконец, правильный запуск gcc выглядит так:

s1# gcc43 test.c -o test -finput-charset=KOI8-R -fexec-charset=KOI8-R &&
./test
Привет!

Можно сам исходник сконвертировать в UTF-8 и вызвать gcc так:

s1# iconv -t UTF-8 test.c > test2.c && gcc43 test2.c -o
test -fexec-charset=KOI8-R && ./test
Привет!

Работает. А если так:

s1# iconv -t UTF-32LE test.c > test2.c && gcc43 test2.c -o
test -finput-charset=UTF-32LE -fexec-charset=KOI8-R && ./test
test2.c:1:19: error: failure to convert UTF-32LE to UTF-8
test2.c: In function 'main':
test2.c:3: warning: incompatible implicit declaration of built-in
function 'printf'

Проблема тут в том, что в аргументе -finput-charset указывается не
только кодировка самой программы test2.c, но и кодировка всех файлов,
подключаемых к ней через #include. В данном примере первой строкой
подключается стандартный заголовочный файл stdio.h, который уж точно не
находится в кодировке UTF-32LE. Поэтому на практике в -finput-charset
можно указать не любую кодировку, а только ту, которая совместима с
кодировкой, в которой находятся заголовочные файлы в /usr/include.

Попутно обнаруживаем, что обработка ошибок при перекодировании из input
charset в source charset оставляет желать лучшего. Если в файле test2.c,
который в кодировке UTF-32LE, в hex-editor'е заменить какой-нибудь
символ на 0xFFFFFFFF (недопустим для этой кодировки), то:

s1# gcc43 test2.c -o test -finput-charset=UTF-32LE -fexec-charset=KOI8-R
&& ./test
cc1: internal compiler error: Segmentation fault: 11
Please submit a full bug report,
with preprocessed source if appropriate.
See <http://gcc.gnu.org/bugs.html> for instructions.

Или даже проще:

s1# gcc43 test.c -o
test -finput-charset=abrakadabra -fexec-charset=KOI8-R && ./test
cc1: internal compiler error: Segmentation fault: 11
Please submit a full bug report,
with preprocessed source if appropriate.
See <http://gcc.gnu.org/bugs.html> for instructions.

Hаличие в gcc двух кодировок (input charset и source charset)
наталкивает на мысль, что когда-то планировалось сделать возможность
указывать кодировку индивидуально для каждого файла (input charset), а
перед компиляцией приводить все кодировки к одной (source charset). В
вышеприведенном комментарии даже есть отсылка на конструкцию

#pragma GCC encoding <xyz>

которая бы позволяла в начале файла указать его кодировку. Однако это не
реализовано.

Теперь проверяем работу с широкими строками. Hо сначала несколько слов о
типе wchar_t. Этот тип нужно рассматривать как обычный целочисленный тип
языка C, размерность которого может быть разной в разных системах, но в
юниксах равна 32 битам (аналог типа int). Этот тип хранится в памяти так
же, как и тип int, т.е. байты идут в том порядке, который принят в
конкретной системе. Именно в таком формате его и ожидают на входе
функции mbtowc()/wctomb() стандартной библиотеки C. Вопрос в другом -
что за числа хранятся в этом типе. По стандарту C99 тут есть всего два
варианта - либо это коды символов юникода, либо это может быть все что
угодно и оно определяется реализацией библиотеки libc в конкретной
системе. В первом случае при компиляции программы значение макроса
__STDC_ISO_10646__ определено, во втором случае - нет. Очевидно, что
если в wchar_t всегда хранить символы юникода, то функции
mbtowc()/wctomb() должны работать со множеством таблиц перекодировки в
Юникод и обратно. Причем для каждой 8-битной кодировки эти таблицы будут
разными. Зато такая программа, будучи скомпилированной один раз, может
работать в любой кодировке локали, даже если эти кодировки менять на
лету. Это случай GNU libc в линуксе. С другой стороны, функции
mbtowc()/wctomb() могут быть реализованы более просто, когда таблиц
перекодировки нет, а есть только общие алгоритмы перевода multibyte
символов в wide и обратно. При этом одна функция способна переводить в
wchar_t любую 8-битную кодировку простым присваиванием wc = mbc. Hо при
этом программа будет корректно работать только в той кодировке локали,
для которой она была скомпилирована. Это случай FreeBSD libc.

Итак, программа test.c в KOI8-R для тестирования широких строк:

#include <stdio.h>
#include <locale.h>
#include <err.h>
int main() {
setlocale(LC_CTYPE, "");
if (wprintf(L"Привет!\n") < 0)
err(1, NULL);
}

s1# gcc43 test.c -o test && ./test
test.c:6:17: error: converting to execution character set: Illegal byte
sequence

С ходу получили ошибку. Здесь текст в KOI8-R проходит следующий
конвейер: input charset (UTF-8) -> source charset (UTF-8) -> wide
charset (UTF-32). Процесс компиляции стопорится на преобразовании UTF-8
в UTF-32, потому что на входе никакой не UTF-8. Укажем правильную
кодировку исходника:

s1# gcc43 test.c -o test -finput-charset=KOI8-R && ./test
!
test: Illegal byte sequence

Компиляция прошла успешно, но теперь ошибку возвращает wprintf().
(Кстати, почему восклицательный знак все же напечатался на stdout? Что
это - баг в wprintf() или так задумано by design?). А ошибка тут
возникла из-за того, что gcc сохранил строку в бинарнике в UTF-32, а
функция wctomb() или ее аналог, которая неявно вызывается из wprintf()
для преобразования широких символов в узкие, ожидает, что в wchar_t
хранятся коды символов в кодировке KOI8-R в соответствии с кодировкой
локали. Поскольку русские символы в юникоде имеют коды больше 255, то и
возникает EILSEQ.

Решение здесь одно - заставить gcc сохранять широкие строки в том
формате, в котором их ожидает увидеть функция wctomb(). А поскольку этот
формат в общем случае зависит от системной libc, gcc должен при сохрании
широких строк вызывать функцию mbtowc(), чего он не делает. Зато он
может вызывать iconv(), а libiconv среди прочих содержит кодировку
"wchar_t", конвертация в которую как раз и происходит через вызов
mbtowc(). Пробуем:

s1# gcc43 test.c -o
test -finput-charset=KOI8-R -fwide-exec-charset=wchar_t && ./test
test.c:6:17: error: converting to execution character set: Illegal byte
sequence

Финиш. Оказывается, когда в gcc отключили определение кодировки
исходников по кодировке локали, попутно отключили и единственный вызов
setlocale(LC_CTYPE, ""). Соответственно iconv() пытается преобразовать
UTF-8 в 7-битный US-ASCII, который соответствует дефолтной локали C. И
обламывается на русских символах. Я пытался задавать переменную
окружения ENABLE_STARTUP_LOCALE="" перед вызовом gcc, но видимо эта фича
давно устарела и не поддерживается.

Такое ощущение, что либо конструкции L'...' и L"..." с символами,
отличными от ASCII, мало кто использует в программах, либо все те, кто
их используют, работают в локали UTF-8. Мне удалось нормально
скомпилировать и запустить программу под FreeBSD только после включения
в исходниках gcc setlocale(LC_CTYPE, "").

Для полноты картины можно убедиться, что в локали UTF-8 gcc корректно
компилирует программу с дефолтными настройками кодировок:

s1# iconv -t UTF-8 test.c > test2.c && gcc43 test2.c -o test && env
LANG=ru_RU.UTF-8 ./test
п?я─п╦п╡п╣я┌!

И в заключение о различных способах задания символьных констант ('...' и
L'...') и строковых литералов ("...", L"..."). Каждый символ внутри
одинарных или двойных кавычек (за исключением
escape-последовательностей) задается в input charset и в процессе
компиляции перекодируется через source charset в соответствующий символ
из execution charset (narrow или wide). Причем если в качестве narrow
charset используется многобайтовая кодировка (например, UTF-8), то
полученный символ может быть представлен в виде последовательности из
нескольких байтов. Escape-последовательности UCN задают коды символов в
юникоде. Эти коды в процессе компиляции конвертируются из юникода в
соответствующие символы в execution charset. Escape-последовательности
типа \n, \t задаются в source charset и в процессе компиляции
конвертируются в execution charset. Однако на практике в разных
кодировках эти коды имеют одни и те же значения. Восьмеричные и
шестнадцатеричные escape-последовательности помещаются в результирующую
строку напрямую без каких-либо перекодировок. Причем каждая такая
escape-последовательность помещается ровно в один элемент char или
wchar_t результирующей строки. Это позволяет формировать строки в жестко
заданных кодировках или безотносительно к кодировкам вообще.

Стандартом C допускается странный синтаксис задания символьных констант,
когда одиночный символ задается в виде нескольких символов - 'abc' или
L'abc'. Причем смысл таких конструкций должен определяться реализацией
компилятора. gcc допускает такой синтаксис, однако я совершенно не
представляю, что это означает и зачем это нужно вообще. По-моему, это
только вносит путаницу.

--
Best regards, Vadim Guchenko [yhw at relost dot net].
Artem Chuprina
2009-09-14 17:27:00 UTC
Permalink
Vadim Guchenko -> Artem Chuprina @ Mon, 14 Sep 2009 15:36:38 +0000 (UTC):

VG> Hаписанное ниже является результатом изучения различной информации в
VG> сети о кодировках и их поддержке в языке C, стандарта C99 и Rationale к
VG> нему, исходников gcc, libiconv и libc.

Большое спасибо за проделанную работу. Будем знать.
--
Intel - тоже Сильмарилл. Только сделанный не так...
Igor S Chencov
2009-09-15 06:59:25 UTC
Permalink
Post by Vadim Guchenko
Hello, Artem!
Hаписанное ниже является результатом изучения различной информации в
сети о кодировках и их поддержке в языке C, стандарта C99 и Rationale к
нему, исходников gcc, libiconv и libc.
[[skip]]

Класс !!!
Опубликуй где-нибудь !!!

Удачи !
P.S. И ссылку сюда кинь ! ;-)
--
Игорь Ченцов
Vadim Guchenko
2009-09-15 07:51:09 UTC
Permalink
Hello, Igor!
You wrote to Vadim Guchenko on Tue, 15 Sep 2009 06:59:25 +0000 (UTC):

IS> Опубликуй где-нибудь !!!
IS> Удачи !
IS> P.S. И ссылку сюда кинь ! ;-)

По сути оно уже опубликовано гуглом:

http://groups.google.com/group/fido7.ru.unix.prog/msg/24372ccf4e9c85da


--
Best regards, Vadim Guchenko [yhw at relost dot net].
Vladimir N. Oleynik
2009-09-15 19:00:57 UTC
Permalink
Здарофъ, Vadim


VG> Hаписанное ниже является результатом изучения

Фундаментально! :-0

VG> вышеприведенном комментарии даже есть отсылка на конструкцию

VG> #pragma GCC encoding <xyz>

VG> которая бы позволяла в начале файла указать его кодировку.
VG> Однако это не реализовано.

А как Вы представляете эту реализацию? Ведь тогда придётся зафиксировать
кодировку до и для первого включения этой строки, например UTF-8, а уж
далее писать в указанной кодировке, например UTF-32.
Файлы в двух кодировках... Кошмар.
А далее начинаются пляски - делать ли эту прагму глобальной или
локальной на файл, в какой кодировке и какими функциями обрабатывать
полученные литералы из подключаемых файлов (какой кошмар для autoconf)...
Мож лучше всё таки без этой прагмы?


--w
vodz
Valentin Nechayev
2009-09-15 19:46:09 UTC
Permalink
VG>> которая бы позволяла в начале файла указать его кодировку.
VG>> Однако это не реализовано.
VNO> А как Вы представляете эту реализацию? Ведь тогда придётся зафиксировать
VNO> кодировку до и для первого включения этой строки, например UTF-8, а уж
VNO> далее писать в указанной кодировке, например UTF-32.
VNO> Файлы в двух кодировках... Кошмар.

Hичего кошмарного нет, давно отработано идеологически, например, в
Python. Там до строки с encoding'ом исходника символы за пределами
ASCII просто не допускаются, будет ошибка компиляции. А так только
задана кодировка - включается разрешение и трансляция в нужное.

(А если уже были символы за пределами ASCII - поднять на это флажок и
при любом переопределении на кодировку, отличную от UTF-8, крэшить
парсинг.)

VNO> А далее начинаются пляски - делать ли эту прагму глобальной или
VNO> локальной на файл,

Локальной, конечно.

VNO> в какой кодировке и какими функциями обрабатывать
VNO> полученные литералы из подключаемых файлов (какой кошмар для autoconf)...

А вот тут нормировать в пределах того, с чем работает autoconf.

VNO> Мож лучше всё таки без этой прагмы?

Hе так всё страшно.:)


--netch--
Serguei E. Leontiev
2009-09-15 20:26:17 UTC
Permalink
Здравствуй Valentin,

Valentin Nechayev -> Vladimir N. Oleynik @ Tue 15-Sep-09 23:46 MSD:

VN> Python. Там до строки с encoding'ом исходника символы за пределами
VN> ASCII просто не допускаются, будет ошибка компиляции. А так только
VN> задана кодировка - включается разрешение и трансляция в нужное.

Строго говоря, лексический анализатор не справится на любимой
z/OS. EBCDIC не далеко, как кажется. :)
--
Успехов, Сергей Леонтьев. E-mail: ***@CryptoPro.ru <http://www.cryptopro.ru>
Valentin Nechayev
2009-09-16 04:59:50 UTC
Permalink
VN>> Python. Там до строки с encoding'ом исходника символы за пределами
VN>> ASCII просто не допускаются, будет ошибка компиляции. А так только
VN>> задана кодировка - включается разрешение и трансляция в нужное.
SEL> Строго говоря, лексический анализатор не справится на любимой
SEL> z/OS. EBCDIC не далеко, как кажется. :)

Случай BCD/EBCDIC/etc. можно и нужно рассматривать отдельно. Особенно
в контексте C - когда у него даже не выполняется требование
непрерывности диапазонов [A-Z] и [a-z], Си формально не должен
существовать на этом:)


--netch--
Serguei E. Leontiev
2009-09-16 05:58:43 UTC
Permalink
Здравствуй Valentin,

Valentin Nechayev -> Serguei E. Leontiev @ ср 16-сен-09 08:59 MSD:

VN>>> Python. Там до строки с encoding'ом исходника символы за пределами
VN>>> ASCII просто не допускаются, будет ошибка компиляции. А так только
VN>>> задана кодировка - включается разрешение и трансляция в нужное.
SEL>> Строго говоря, лексический анализатор не справится на любимой
SEL>> z/OS. EBCDIC не далеко, как кажется. :)

VN> Случай BCD/EBCDIC/etc. можно и нужно рассматривать отдельно. Особенно
VN> в контексте C - когда у него даже не выполняется требование
VN> непрерывности диапазонов [A-Z] и [a-z], Си формально не должен
VN> существовать на этом:)

Как говорилось в бородатом анекдоте: "...А в личную жизнь моей кавказкой
овчарки не лезьте...". :)

Быть может, моя память коротка или я просто умишком слаб, но C99 вообще
на это не закладывается. Что ж касается POSIX, так в нём диапазоны
регулярных выражений должны работать только от "locale".

Что до z/OS - так IBM регулярно его стратифицировала, и на Single Unix
Specification, и на POSIX. (типа "Лучший Unix - это z/OS":)

Hет, это конечно для меня было большой неожиданностью, что tar архив
раскрывает, ls видит, а cat файлы не показывает и скрипты инсталлятора
просто так не ходят и, как красны девицы, требуют внимания и содержат
ошибки.

Это ты от Open Source натерпелся, в самом деле, не на всякой системе, не
на всякой "locale" и не во всяком редакторе поиск "[А-Я]" найдёт всё что
по POSIX положено найти. :)
--
Успехов, Сергей Леонтьев. E-mail: ***@CryptoPro.ru <http://www.cryptopro.ru>
Serguei E. Leontiev
2009-09-15 20:42:22 UTC
Permalink
Здравствуй Vladimir,

Vladimir N. Oleynik -> Vadim Guchenko @ Tue 15-Sep-09 23:00 MSD:

VG>> вышеприведенном комментарии даже есть отсылка на конструкцию
VG>> #pragma GCC encoding <xyz>
VG>> которая бы позволяла в начале файла указать его кодировку.
VG>> Однако это не реализовано.

VNO> А как Вы представляете эту реализацию? Ведь тогда придётся зафиксировать
VNO> кодировку до и для первого включения этой строки, например UTF-8, а уж
VNO> далее писать в указанной кодировке, например UTF-32.
VNO> Файлы в двух кодировках... Кошмар.

Да ладно, это то дело то обычное, опыт работы с двумя кодировками
есть. Hапример, XML или тот же MIME, которым я сейчас и пишу.

VNO> А далее начинаются пляски - делать ли эту прагму глобальной или
VNO> локальной на файл, в какой кодировке и какими функциями обрабатывать
VNO> полученные литералы из подключаемых файлов (какой кошмар для autoconf)...

Совершенно верно, прагма такого типа может решить проблему для
реализаций типа __STDC_ISO_10646__, в противном случае следует указывать
не "encoding"/"charset", а "locale". А вот если начать указывать
"locale", то тут то и возникнут эти проблемы. Я так думаю.
--
Успехов, Сергей Леонтьев. E-mail: ***@CryptoPro.ru <http://www.cryptopro.ru>
Vadim Guchenko
2009-09-16 08:36:02 UTC
Permalink
Hello, Vladimir!
You wrote to Vadim Guchenko on Tue, 15 Sep 2009 19:00:57 +0000 (UTC):

VG>> вышеприведенном комментарии даже есть отсылка на конструкцию
VG>> #pragma GCC encoding <xyz>
VG>> которая бы позволяла в начале файла указать его кодировку.
VG>> Однако это не реализовано.
VNO> А как Вы представляете эту реализацию? Ведь тогда придётся
VNO> зафиксировать кодировку до и для первого включения этой строки,
VNO> например UTF-8, а уж далее писать в указанной кодировке,
VNO> например UTF-32.
VNO> Файлы в двух кодировках... Кошмар.
VNO> А далее начинаются пляски - делать ли эту прагму глобальной или
VNO> локальной на файл, в какой кодировке и какими функциями
VNO> обрабатывать полученные литералы из подключаемых файлов (какой
VNO> кошмар для autoconf)...
VNO> Мож лучше всё таки без этой прагмы?

В принципе алгоритм был приведен в том самом комментарии, который
отключал setlocale():

We should definitely recognize in-band markers of file encoding, like:
- the appropriate Unicode byte-order mark (FE FF) to recognize
UTF16 and UCS4 (in both big-endian and little-endian flavors)
and UTF8
- a "#i", "#d", "/ *", "//", " #p" or "#p" (for #pragma) to
distinguish ASCII and EBCDIC.
- now we can parse something like "#pragma GCC encoding <xyz>
on the first line.

Я не уверен, что поддержка исходных текстов программ в таких кодировках,
как UTF-16 и UTF-32, вообще нужна в gcc. Во-первых, эти кодировки не
могут использоваться в качестве narrow execution charset по стандарту C.
Это значит, что вряд ли эти кодировки будут использоваться в локали (а
есть реальные примеры, где это так?). А раз локаль не в UTF-16 и не в
UTF-32, то кому придет в голову сохранять исходники в этих кодировках?
Во-вторых, в коде gcc есть автоопределение narrow execution charset, в
которой он сам компилируется. И если это кодировка не совместима с ASCII
или EBCDIC, компиляция должна прерваться. Так что вполне в духе gcc
считать, что любые исходники могут быть написаны в кодировке,
совместимой либо с ASCII, либо с EBCDIC. Или только с ASCII, т.к. EBCDIC
не поддерживает даже libiconv. Поэтому прагма, уточняющая кодировку,
может быть легко прочитана в начале файла. Это частный случай.

В более общем случае можно использовать допущение, что все исходники
отдельной библиотеки или программы как правило пишутся в одной
кодировке. Hазвание этой кодировки можно указывать в файле со
специальным именем (скажем, ".charset") в кодировке US-ASCII, который
находится в том же каталоге, что и компилируемый исходник. Тогда автор
может писать свою программу хоть на иврите с десятью байтами на символ,
лишь бы эту кодировку поддерживал libiconv. Автору нужно один раз
озаботиться созданием файлика .charset в кодировке ASCII и положить его
во все каталоги проекта. То же самое касается и системных каталогов
/usr/include. Только здесь проблема может быть в том, что сторонняя
библиотека, устанавливая свой заголовочный файл в /usr/local/include,
желает указать для него одну кодировку, а скажем уже существующие
заголовочные файлы от других библиотек используют другую. А файл
.charset общий на весь каталог /usr/local/include. В этом случае можно
или устанавливать каждый заголовочный файл в отдельный подкаталог со
своим ".charset", или поддерживать также файлы вида
"original_filename.charset", которые бы задавали кодировку только для
одного файла "original_filename". Тогда компилятор перед чтением любого
файла, переданного ему через командную строку или подключенного через
#include, мог бы сначала поискать в том же каталоге, где находится этот
файл, файл с таким же именем, но суффиксом ".charset", потом файл с
именем ".charset", потом посмотреть на кодировку, указанную в командной
строке, потом взять кодировку локали, и если ничего не сработало,
использовать дефолтную кодировку. Это бы решило проблему разных input
charset в общем случае. По сути это out-band метод задания кодировки для
файла.

Однако input charset - не единственный источник бед. Проблемы может
доставить и execution charset, которая опять же задается в gcc
глобально. Предположим некая программа была написана и отлажена в
системе, где кодировка локали совместима с ASCII. Все сообщения, которая
эта программа выводит на локаль, на английском языке и зашиты прямо в
программу в узких строковых литералах "". Локализация не предусмотрена,
но не в этом суть. Сама программа общается по некоторому текстовому
протоколу с удаленным сервером. Команды этого протокола тоже находятся в
кодировке ASCII и тоже жестко зашиты в программу ("HELO", "MAIL TO" и
т.д.). Теперь что произойдет, если эту же программу попытаться
скомпилировать в системе с EBCDIC? Предположим, что в gcc можно было бы
указать EBCDIC в качестве narrow execution charset (точнее, что libiconv
бы ее поддерживал). Тогда все строковые литералы программы при
компиляции сконвертировались бы из ASCII в EBCDIC. И хотя программа по
прежнему выводила бы свои сообщения на читаемом английском языке в
локаль с кодировкой EBCDIC, работать по сети она бы перестала, т.к. все
сетевые команды внутри бинарника тоже были бы переведены в кодировку
EBCDIC. Поэтому нужно или в исходниках писать не "HELO", а что-нибудь
типа "\x48\x45\x4c\x4f", что явно не способствует удобочитаемости, или
иметь способ указывать для отдельных символьных констант и строковых
литералов, блоков кода или хотя бы отдельных файлов свой execution
charset.

--
Best regards, Vadim Guchenko [yhw at relost dot net].
Serguei E. Leontiev
2009-09-16 15:59:25 UTC
Permalink
Здравствуй Vadim,

Vadim Guchenko -> Vladimir N. Oleynik @ ср 16-сен-09 12:36 MSD:

VG> Я не уверен, что поддержка исходных текстов программ в таких кодировках,
VG> как UTF-16 и UTF-32, вообще нужна в gcc. Во-первых, эти кодировки не
VG> могут использоваться в качестве narrow execution charset по
VG> стандарту C.

Формально стандарт не запрещает реализации у которых CHAR_BIT == 16 или
CHAR_BIT == 32.

VG> Это значит, что вряд ли эти кодировки будут использоваться в локали (а
VG> есть реальные примеры, где это так?). А раз локаль не в UTF-16 и не в
VG> UTF-32, то кому придет в голову сохранять исходники в этих
VG> кодировках?

Чужая голова потёмки, но некоторым может и прийти. Hе вызывает же
отторжения мысль, что "locale" KOI8-R, а файл в UTF-8. Поэтому есть и
ещё будут системы в которых можно хранить файл в таких кодировках.

VG> совместимой либо с ASCII, либо с EBCDIC. Или только с ASCII,
VG> т.к. EBCDIC не поддерживает даже libiconv. Поэтому прагма,

Эк оно как всё запущенно, т.е. dd поддерживает, а iconv нет? Прикольно.
--
Успехов, Сергей Леонтьев. E-mail: ***@CryptoPro.ru <http://www.cryptopro.ru>
Vadim Guchenko
2009-08-14 08:23:31 UTC
Permalink
Hello, Artem!
You wrote to Vadim Guchenko on Thu, 13 Aug 2009 11:56:46 +0000 (UTC):

AC> Чтобы все аккуратно работало, надо получить этот символ из
AC> mbtowc(). Hа самом деле по умолчанию во фрюхе ни разу не UTF-32
AC> :-) Как показала практика.

Посмотрел в исходники libc на FreeBSD 7.1. Внутреннее представление
символов в типе wchar_t зависит от кодировки, из которой они получены.
UTF-8 конвертируется в UTF-32BE. Каждый символ обычной однобайтовой
кодировки типа KOI8-R просто расширяется нулями до 32 битов. Другие
кодировки имеют свои правила преобразования в wchar_t. Hо для меня
по-прежнему остается непонятным, по какому алгоритму компилятор
переводит в wchar_t константы L'' и L"". Тем более, что в реализациях
самих функций mbtowc() для разных кодировок используется конструкция
L'\0'.

--
Best regards, Vadim Guchenko [yhw at relost dot net].
Ilya Anfimov
2009-08-13 12:27:54 UTC
Permalink
Post by Vadim Guchenko
Hello, All!
Тип wchar_t в стандартах определен так, что он должен вмещать код любого
символа любой кодировки, поддерживаемой системой, однако ничего не
сказано о формате хранения данных в этом типе. С одной стороны
утверждается, что это внутренний тип данных языка C, и формат хранения
данных в нем определяется компилятором, а для приложения этот формат не
имеет значения. И действительно, в разных осях этот тип имеет даже
разный размер (в винде - 2 байта, в юниксах - 4). Причем gcc имеет два
ключа, один из которых влияет на размер типа wchar_t (-fshort-wchar), а
второй, как я понял, позволяет задать кодировку символов внутри типа
wchar_t (-fwide-exec-charset). И хотя по умолчанию эта кодировка UTF-32
или UTF-16 в зависимости от выбранного размера wchar_t, теоретически там
можно указать любую кодировку, даже не связанную с юникодом. С другой
стороны стандартная библиотека C содержит множество функций, принимающих
и возвращающих тип wchar_t, в том числе преобразующих wide character в
multibyte character и обратно. А для такого преобразования функции
должны знать не только кодировку для multibyte characters, которая
определяется по текущей локали, но и кодировку символов внутри типа
wchar_t.
Собственно вопрос: в какой кодировке должны быть символы в wchar_t для
корректной работы стандартных библиотечных функций FreeBSD и где об этом
написано? Более конкретно: в какой кодировке нужно передать символ
wchar_t в функцию wctomb(), чтобы на выходе получить этот же символ в
В той, которая получилась преобразованием mbtowc()
Post by Vadim Guchenko
кодировке текущей локали?
--
Best regards, Vadim Guchenko [yhw at relost dot net].
Loading...