LINUX.ORG.RU

Нужна помощь в вводе юникодной строки в программу C

 , ,


0

1

Гуглил, нашел только вывод юникодной строки. Вывод (printf, puts) работает, ввод латиницы (gets, fgets, scanf, fgetws) тоже.

Компилятор GCC, стандарт c17.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>

int main() {
    char name[256]; char agestr[3]; int age;
    printf("Привет, как вас зовут? ");
    fgetws(name, sizeof(name), stdin); // по идее fgetws должен уметь читать юникод
    printf("Очень приятно, %s, сколько вам лет? ", name); // если в name кириллица, то ничего не выводит вместо %s
    fgets(agestr, sizeof(agestr), stdin);
    age = atoi(agestr);
    if (age == 18)
        printf("Мне столько же! Просто прекрасно!");
    else
        printf("\nМне 18 лет. Я на %i лет/года %s вас!", abs(age-18), ((age>18)? "младше" : "старше"));
}

Во-первых, в процессе должна быть установлена локаль:

#include <locale.h>

int
main(...
...
       setlocale(LC_ALL, ""); // or, at least, LC_CTYPE
...

Здесь подразумевается, что в системе (или для конкретного юзера) используется одна из UTF-8, проверить - locale.

Во-вторых, wc/ws (wide character/string) и mb/mbs (multibyte character/string) это разные вещи. Если читаешь ws, то и выводить нужно ws, %S или %ls, смотри в документации printf(3).

dsdqmhsx
()
Ответ на: комментарий от Ivan_qrt

Что за conio.h не знаю

https://ru.wikipedia.org/wiki/Conio.h

Сonio.h (от англ. console input-output — консольный ввод-вывод) — заголовочный файл, доступный в некоторых средах разработки для MS-DOS и Windows. Предназначен для организации текстового ввода-вывода в этих операционных системах. Conio.h не является частью языка программирования Си, стандартной библиотеки языка Си или ISO C, его наличия не требует стандарт POSIX.

fsb4000 ★★★★★
()
Ответ на: комментарий от tongubin

Это работает из коробки, если всё делать последовательно и не смешивать типы байт и строк.

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

int main() {
    char name[256]; char agestr[3]; int age;
    printf("Привет, как вас зовут? ");
    fgets(name, sizeof(name), stdin); // по идее fgetws должен уметь читать юникод
    printf("Очень приятно, %s, сколько вам лет? ", name); // если в name кириллица, то ничего не выводит вместо %s
    fgets(agestr, sizeof(agestr), stdin);
    age = atoi(agestr);
    if (age == 18)
        printf("Мне столько же! Просто прекрасно!");
    else
        printf("\nМне 18 лет. Я на %i лет/года %s вас!\n", abs(age-18), ((age>18)? "младше" : "старше"));
}
Ivan_qrt ★★★★★
()
Ответ на: комментарий от tongubin

И ещё вывод вот этого покажи, посмотрим, какие байтики считываются.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

int main() {
    char name[256]; char agestr[3]; int age;
        memset(name, 0, sizeof(name));
    printf("Привет, как вас зовут? ");
    fgets(name, sizeof(name), stdin); // по идее fgetws должен уметь читать юникод
        for (int i = 0; i < 20; ++i) {
                printf(":%02x", (uint8_t)name[i]);
        }
        printf("\n");
    printf("Очень приятно, %s, сколько вам лет? ", name); // если в name кириллица, то ничего не выводит вместо %s
    fgets(agestr, sizeof(agestr), stdin);
    age = atoi(agestr);
    if (age == 18)
        printf("Мне столько же! Просто прекрасно!");
    else
        printf("\nМне 18 лет. Я на %i лет/года %s вас!\n", abs(age-18), ((age>18)? "младше" : "старше"));
}
Ivan_qrt ★★★★★
()
Ответ на: комментарий от Ivan_qrt

выводит крокозябры.

вывод вашей программы:

Привет, как вас зовут? Лев
:00:00:00:0a:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
Очень приятно, , сколько вам лет? 18
Мне столько же! Просто прекрасно!
tongubin
() автор топика
Ответ на: комментарий от tongubin

А что за линукс у тебя вообще? Какой дистрибутив и что менял в локалях, потому что локаль у тебя не utf-8, а какая-то другая, судя по-всему.

И ещё вывод последнего варианта с export LANG=ru_RU.UTF-8 покажи.

Ivan_qrt ★★★★★
()
Ответ на: комментарий от Ivan_qrt

Arch linux.

в locale.conf и locale.gen стоят ru_RU.UTF-8.

кодировка файла utf-8.

команда сборки gcc main.c -o cprogram, запускаю cprogram.

после export LANG=ru_RU.UTF-8:

lev@tongubin-laptop:cprogram$ ./cprogram
╨ƒ╤Ç╨╕╨▓╨╡╤é, ╨║╨░╨║ ╨▓╨░╤ü ╨╖╨╛╨▓╤â╤é? Лев                                                                             :3f:3f:3f:0a:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00                                                            ╨₧╤ç╨╡╨╜╤î ╨┐╤Ç╨╕╤Å╤é╨╜╨╛, ???                                                                                          , ╤ü╨║╨╛╨╗╤î╨║╨╛ ╨▓╨░╨╝ ╨╗╨╡╤é? 18                                                                                      ╨£╨╜╨╡ ╤ü╤é╨╛╨╗╤î╨║╨╛ ╨╢╨╡! ╨ƒ╤Ç╨╛╤ü╤é╨╛ ╨┐╤Ç╨╡╨║╤Ç╨░╤ü╨╜╨╛!
tongubin
() автор топика
Последнее исправление: tongubin (всего исправлений: 1)
Ответ на: комментарий от tongubin

Кирилица != unicode

В простейшем случае, как выше написали, ничего дополнительно для этого делать не нужно. Учитывая, что пример у тебя учебный, скорее всего глубже тебе сюда погружаться и не нужно.

Если интересно погрузиться глубже, ключевые слова и типы данных я тебе выше уже закинул.

faq2
()
Ответ на: комментарий от tongubin

Вообще нихрена не понятно. С локалями точно что-то не так. Я бы попробовал в виртуалке или ещё где запустить лайв федоры/убунты и запустить бинарь в нём. Так по-крайней мере понятно станет.

Ну или вообще в виртуалку федору/убунту поставить с компилятором и попробовать собрать/запустить там.

В моих экспериментах выставляемая локаль на кодировку строк вообще не влияет. Ни в исходниках, ни в переменных среды. А вот кодировка файла, понятное дело влияет. У тебя оно работает как-то по-другому, а как именно для меня загадка.

Ivan_qrt ★★★★★
()
fgetws(name, sizeof(name), stdin);
fgets(agestr, sizeof(agestr), stdin);

4 Each stream has an orientation. After a stream is associated with an external file, but before any operations are performed on it, the stream is without orientation. Once a wide character input/output function has been applied to a stream without orientation, the stream becomes a wide-oriented stream…

5 Byte input/output functions shall not be applied to a wide-oriented stream…

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

И какие же есть не байтовые функции i/o? Нам на уроке давали только эти и мы делали задания с латиницей, а на дом задали кириллицу и не сказали, чем можно заменить. Преподаватель сам не знает

tongubin
() автор топика

Поскольку твой Linux скорее всего Windows судя по этому треду или ты wine-gcc собираешь, но тогда разницы не много с Windows, то попробуй так:

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

int main() {
    setlocale(LC_ALL, ".866"); // or, at least, LC_CTYPE

    char name[256]; char agestr[3]; int age;
    printf("Привет, как вас зовут? ");
    fgets(name, sizeof(name), stdin); // по идее fgetws должен уметь читать юникод
    {
        const size_t ln = strlen(name);
        if (ln > 0 && name[ln-1] == '\n')
            name[ln-1] = '\0';
    }
    printf("Очень приятно, %s, сколько вам лет? ", name); // если в name кириллица, то ничего не выводит вместо %s
    fgets(agestr, sizeof(agestr), stdin);
    age = atoi(agestr);
    if (age == 18)
        printf("Мне столько же! Просто прекрасно!");
    else
        printf("Мне 18 лет. Я на %i лет/года %s вас!", abs(age-18), ((age>18)? "младше" : "старше"));
}

Файл сохраняй в кодировке CP866.

У меня работает: https://imgur.com/a/gkw2EyL

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Файл сохраняй в кодировке CP866.

в копилку вредных советов :-) зато у ТС должно сработать, у него очевидная проблема с кодировками, исходник vs windows консоль vs windows gui (у винды поболее двух кодировок сразу).

особенности отечественного IT образования - в преподаватели попали те кто не вписался в прочий процесс.

в топике факт - препод не смог объяснить как, где и зачем пишутся программы. Преподаёт сферичного коня

MKuznetsov ★★★★★
()
Ответ на: комментарий от tongubin

Вот и не надо. Никаких wide char'ов
Если нужен utf16 или utf32 - uint16_t и uint32_t
Для преобразования кодировки какой-нидбудь iconv или накопипсть конвертер откуда-нибудь между юникодами.
Для современнной винды тоже utf-8, она умеет. Для старой - конверти в utf-16 и используй unicode версии функций - так будешь уверен что оно не будет ломаться в зависимости от локали/версии (тут конечно wide char'ы, но в пределах платформы _WIN32 оно не должно посыпаться если она вообще юникод поддерживает)

mittorn ★★★★★
()
Последнее исправление: mittorn (всего исправлений: 1)
Ответ на: комментарий от fsb4000

866 была придумана для DOS, ей кодируются имена в файловой системе DOS. Остальные мультибайт текстЫ в Windows это CP-1251. И то и то по факту однобайтные кодировки. CP-1251 используется в консоли винды, 866 вроде бы никому уже не интересна.

При выводе под виндой в консоль текста в однобайтной кодировке, по дефолту будет системная (1251). Если нужна другая, то перед выводом нужен вызов SetThreadLocale, насколько помню. Если лупите в юникоде, то не ошибетесь, wprintf сделает как надо.

В основном, в винде сейчас Unicode в текстовых файлах, включая исходники проектов, 2(4) байта на символ. Хотите задать такую строку в сырце, пишите L"строка". Сохраняя в Visual Studio можно сделать SaveAs и задать кодировку файла.

UTF-8 родная для Linux. Латиница кодируется 1 байтом, остальное последовательностью до 4х что ли байт. Причем strlen выдаст длинну в байтах как есть, а не в символах. printf нормально выведет UTF-8 строку.

Сдается, что у Вас путаница с кодировкой исходника. Посмотрите в hex виде исходник. Редакторы оставляют и лояльно относятся когда это делают другие редакторы, т.наз BOM (Byte Order Mode) в начале файла. В юникоде найдется FE FF, в UTF-8 тож какая-то сигнатура на 3 байта. И gcc и VS компилеры тоже спокойно относятся к BOM.

Для конвертации файла целиком есть утиля iconv. Переводы строк в стиле Win/Lin конвертируются утилями dos2unix/unix2dos.

bugs-bunny
()

А ваще, взять в руки отладчик и посмотреть как представлены строки побайтно? Что в коде как строки забито, что дает fgets. С минимальными знаниями о кодировках можно понять что к чему.

bugs-bunny
()
Ответ на: комментарий от utf8nowhere

На printf(«Привет мир\r\n»); компилер сгенерит CP-1251 и выведет его благополучно. Или нет?

866 генерили какие-нить BorlandC или QuickC под DOS, 16-битные exe-шники. Так такие теперь даже не запустить после Vista и 7-ки. Разрабы ОС забыли про режим проца vm86 и DOS наследие.

bugs-bunny
()
Ответ на: комментарий от MKuznetsov

в копилку вредных советов :-)

Ok.

@tongubin сохраняй файл как UTF-8, только компилируй ещё с двумя опциями:

gcc   test.c -finput-charset=UTF-8 -fexec-charset=cp866 -o test

У меня так тоже работает.

fsb4000 ★★★★★
()
Ответ на: комментарий от bugs-bunny

В венде консоль считается для совместимости с DOS, так что там соответствующая кодировка.

Если что-то и поменялось по-дефолту, то максимум на UTF-8, а не на вендовую однобайтовую.

utf8nowhere ★★★
()
Ответ на: комментарий от fsb4000

У меня так тоже работает.

помниться что в винде для консоли стоит отказаться от printf и перелезть на wprintf, wXXX . Строковые константы предназначенные для вывода в консоль объявлять как wchat_t s=L"строчко" - это как раз инструкция компилятору что литерал строчко переделать в 16-бит wchar UTF16 (про BE/LE затруднюсь сказать);

во Gtk & Qt сие делается под капотом из utf-8 потому незаметно и переносимо.

MKuznetsov ★★★★★
()