локализация строк в C-коде

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 28 авг 2013, 23:36

Виктория писал(а):В качестве примеров я брала файлы с расширением py :-)
Это, наверное, имеется в виду: viewtopic.php?f=31&t=3029&start=10#p8988 ?

Но чтобы не создавать путаницу (ведь здесь тема называется "локализация строк в C-коде" ;-) ) давайте я создам отдельную коротенькую ему по локализации в Python, и там попробуем разобраться.
Чтобы снять всякие сомнения ... в возможности лёгкого переноса проектов между Linux & Windows ... и какие там неожиданности могут подстерегать.

Вот такая тема: локализация в Python

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 09 ноя 2013, 18:46

Olej писал(а): 1. "широкие" символы wchar_t, которые являются (в POSIX) прямым изображением UNICODE кодировки UTF-32, и, естественно, каждый символ занимает 4 байта;

2. многобайтные представления, которые хоть и загружены в char[], но в кодировке UTF-8 (т.е. тоже UNICODE, но в другой кодировке); они непригодны для непосредственной обработки строковыми функциями + функции преобразований (mbtowc(), wctomb(), mblen(), mbrtowc(), wcrtomb(), mbrlen() и т.д.), для преобразования в wchat_t[] и обратно (См.: Многобайтовые символы, mbchar(3C))
Вот неплохое (простое) изложение соотношения мультибайтного представления (UTF-8) символов и широкого (wchar_t): 18. Расширение Символов (это из первоисточника описывающего C библиотеки GNU/GCC).
Вполне достаточно для того, чтобы делать для себя преобразования из одной формы в другую.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 09 ноя 2013, 19:09

Olej писал(а):
Olej писал(а): 1. "широкие" символы wchar_t, которые являются (в POSIX) прямым изображением UNICODE кодировки UTF-32, и, естественно, каждый символ занимает 4 байта;

2. многобайтные представления, которые хоть и загружены в char[], но в кодировке UTF-8 (т.е. тоже UNICODE, но в другой кодировке); они непригодны для непосредственной обработки строковыми функциями + функции преобразований (mbtowc(), wctomb(), mblen(), mbrtowc(), wcrtomb(), mbrlen() и т.д.), для преобразования в wchat_t[] и обратно (См.: Многобайтовые символы, mbchar(3C))
Вот неплохое (простое) изложение соотношения мультибайтного представления (UTF-8) символов и широкого (wchar_t): 18. Расширение Символов.
Вполне достаточно для того, чтобы делать для себя преобразования из одной формы в другую.
В связи с этим любопытный пример:

Код: Выделить всё

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#include <locale.h>

const char buf[] = "русскоязычная строка";

int main( int argc, char **argv ) {
   wchar_t wbuf[ 40 ];
   printf( "значение MB_CUR_MAX до вызова setlocale(): %d [ locale = %s ]\n",
           MB_CUR_MAX, setlocale( LC_ALL, NULL ) );
   setlocale( LC_ALL, "" );   // только после этого работают преобразования!
   printf( "значение MB_CUR_MAX после вызова setlocale(): %d [ locale = %s ]\n",
           MB_CUR_MAX, setlocale( LC_ALL, NULL ) );
   printf( "исходная UTF-8 строка: '%s', длина в байтах = %d\n", buf, strlen( buf ) );
   int n = -1, i;
   char *p;
   for( i = 0, p = (char*)buf; n != 0; i++ )
      p += ( n = mbtowc( wbuf + i, p, MB_CUR_MAX ) );
   printf( "преобразованная строка: '%ls'\n"
           "длина преобразованной wchar_t строки = %d символов\n",
           wbuf, wcslen( wbuf ) );
   return 0;
}
Любопытного здесь то, что до вызова setlocale( LC_ALL, "" ) в программе установлена локализация C, и никакое преобразование русскоязычного текста (UTF-8) ... да и вообще любая работа с многобайтным представлением русского языка невозможны!
И любой вызов функций mb*() будет возвращать только ошибку.

Код: Выделить всё

olej@notebook:~/2013_WORK/examples.tools.DRAFT/locale_c$ ./loctest 
значение MB_CUR_MAX до вызова setlocale(): 1 [ locale = C ]
значение MB_CUR_MAX после вызова setlocale(): 6 [ locale = ru_UA.UTF-8 ]
исходная UTF-8 строка: 'русскоязычная строка', длина в байтах = 39
преобразованная строка: 'русскоязычная строка'
длина преобразованной wchar_t строки = 20 символов
Вложения
loctest.c
(1.07 КБ) 416 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 07 янв 2015, 22:56

Интересные эффекты с локализацией наблюдаются в Java ... описаны вот здесь: java.выбор_коллекции.
Такие вещи, в общем, не зависимы от языка программирования ... и наводят на вопросы к размышлениям.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 04 фев 2016, 18:16

Ещё раз возвращаясь к локализации.
Причем не только в C, но и C++ ... поскольку там эти вещи обстоят аналогично.

P.S. Напомню, что C++ программы используют (кроме своей собственной libstdc++.so) стандартную библиотеку C/POSIX libc.so - без библиотеки C код C++ недееспособный, он не удет выполняться. В этом легко удостовериться лишний раз, посмотрев на маленькие программки (out1.cc & out5.c), которые ниже рассмотрим:

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ ldd out1
	linux-gate.so.1 =>  (0xb77cf000)
	libstdc++.so.6 => /usr/lib/i386-linux-gnu/libstdc++.so.6 (0xb76c4000)
	libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb76a7000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb74f8000)
	libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb74b2000)
	/lib/ld-linux.so.2 (0xb77d0000)
olej@nvidia ~/2016_WORK/in.WORK/out $ ldd out5
	linux-gate.so.1 =>  (0xb7716000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7545000)
	/lib/ld-linux.so.2 (0xb7717000)

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 04 фев 2016, 18:25

Olej писал(а):Ещё раз возвращаясь к локализации.
Итак:

Код: Выделить всё

int main() {
   locale::global( locale( "" ) );
   wcout << L"строка" << endl;
   return 0;
}

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ ./out1
строка 
Всё замечательно: выводится русский текст, заданный символьной константой в широких символах (UTF-32), отлично выводится на терминал ... и на другие места будет так же ;-) .
Вот так и будем поступать.

Если убрать строку с locale::... - то получим явное не то:

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ ./out1
?????? 
Этой строкой мы устанавливаем локаль программы в ту, которая установлена в системе по умолчанию:

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ locale
LANG=ru_RU.utf8
LANGUAGE=
LC_CTYPE="ru_RU.utf8"
LC_NUMERIC="ru_RU.utf8"
LC_TIME="ru_RU.utf8"
LC_COLLATE="ru_RU.utf8"
LC_MONETARY="ru_RU.utf8"
LC_MESSAGES="ru_RU.utf8"
LC_PAPER="ru_RU.utf8"
LC_NAME="ru_RU.utf8"
LC_ADDRESS="ru_RU.utf8"
LC_TELEPHONE="ru_RU.utf8"
LC_MEASUREMENT="ru_RU.utf8"
LC_IDENTIFICATION="ru_RU.utf8"
LC_ALL=

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 04 фев 2016, 18:31

Olej писал(а):
Olej писал(а):Ещё раз возвращаясь к локализации.
Этой строкой мы устанавливаем локаль программы в ту, которая установлена в системе по умолчанию:
Можно (только осторожно :lol: ) и явно заказать локаль для программы (хоть китайскую):

Код: Выделить всё

   locale loc;
   try {
      loc = std::locale ( "ru_RU.utf8" );
   }
   catch( std::runtime_error ) {
      loc = std::locale ( loc, "", std::locale::ctype );
   }
   locale::global( loc );
Но делаем это в блоке try, потому что если такая локаль не установлена в системе (не установлена, не обязательно является дефаултной), то мы поймаем исключение.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 04 фев 2016, 18:44

Olej писал(а): Вот так и будем поступать.
Но! :

Код: Выделить всё

int main() {
   locale::global( locale( "" ) );
   cout << "строка1" << endl;
   wcout << L"строка2" << endl;
   cout << "строка3" << endl;
   wcout << L"строка4" << endl;
   return 0;
}

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ ./out2a
строка1
AB@>:02
строка3
AB@>:04
И:

Код: Выделить всё

int main() {
   locale::global( locale( "" ) );
   wcout << L"строка1" << endl;
   cout << "строка2" << endl;
   wcout << L"строка3" << endl;
   cout << "строка4" << endl;
   return 0;
}

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ ./out2b
строка1
строка3
В (выходной) поток можно писать только строки одного рода: либо нативные char, либо широкие символы wchar_t. Поток настраивается под первый пришедший тип символов, и вывод другого типа символов идёт вразнос. Причём по-разному: вывод wcout после cout выводит кракозябры (локаль к этому не имеет отношения!), а вывод cout после wcout просто уходит в никуда (/dev/null).

Документация упоминает об этом:
A program should not mix output operations on wcout with output operations on cout (or with other narrow-oriented output operations on stdout): Once an output operation has been performed on either, the standard output stream acquires an orientation (either narrow or wide) that can only be safely changed by calling freopen on stdout.
Исходя из этой фразы мы можем решить проблему:

Код: Выделить всё

int main() {
   locale::global( locale( "" ) );
   cout << "строка1" << endl;
   stdout = freopen( "/dev/stdout", "w", stdout );
   wcout << L"строка2" << endl;
   stdout = freopen( "/dev/stdout", "w", stdout );
   cout << "строка3" << endl;
   stdout = freopen( "/dev/stdout", "w", stdout );
   wcout << L"строка4" << endl;
   return 0;
}

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ ./out3
строка1
строка2
строка3
строка4
P.S. Сразу возьмём на заметку: freopen() - API POSIX, C ... т.е. проблема потоков не в C++, а в C библиотеке.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 04 фев 2016, 19:01

Olej писал(а): Исходя из этой фразы мы можем решить проблему:
Но это решение - архитектурно-зависимое (/dev/sysout).
А как быть тем C++ писателям, которые любят этим заниматься в C++ на Windows :oops: ... или любой другой C++ системе?
В документации тоже ест подсказка:
If filename is a null pointer, the function attempts to change the mode of the stream. Although a particular library implementation is allowed to restrict the changes permitted, and under which circumstances.

Код: Выделить всё

int main() {
   locale::global( locale( "" ) );
   cout << "строка1" << endl;
   stdout = freopen( NULL, "w", stdout );
   wcout << L"строка2" << endl;
   stdout = freopen( NULL, "w", stdout );
   cout << "строка3" << endl;
   stdout = freopen( NULL, "w", stdout );
   wcout << L"строка4" << endl;
   return 0;
}

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ ./out4
строка1
строка2
строка3
строка4

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: локализация строк в C-коде

Непрочитанное сообщение Olej » 04 фев 2016, 19:07

Olej писал(а):P.S. Сразу возьмём на заметку: freopen() - API POSIX, C ... т.е. проблема потоков не в C++, а в C библиотеке.
Разобравшись с C++ (мне кажется что в исчерпывающей мере), самое время вернуться в C:

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ make
cc     out5a.c   -o out5a
cc     out5b.c   -o out5b

Код: Выделить всё

int main() {
   setlocale( LC_ALL, "" );
   char cs[] = "строка";
   wchar_t ws[] = L"строка";
   printf( "%s\n", cs );
   printf( "%ls\n", ws );
   printf( "%s\n", cs );
   printf( "%ls\n", ws );
   return 0;
}

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ ./out5a
строка
строка
строка
строка

Код: Выделить всё

int main() {
   setlocale( LC_ALL, "" );
   char cs[] = "строка";
   wchar_t ws[] = L"строка";
   printf( "%ls\n", ws );
   printf( "%s\n", cs );
   printf( "%ls\n", ws );
   printf( "%s\n", cs );
   return 0;
}

Код: Выделить всё

olej@nvidia ~/2016_WORK/in.WORK/out $ ./out5b
строка
строка
строка
строка
1. setlocale( LC_ALL, "" ); - выполняет здесь те же функции, что и locale::global( locale( "" ) ); в C++ коде.
2. без него работа с широкими символами будет невозможна, и что самое противное - преобразования из UTF-8 в wchar_t функциями группы mb*() - работать не будут.
3. но самый важный результат: printf() будет работать и с типовыми и с широкими символами вперемешку любым образом.

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 4 гостя