5

Решил написать небольшой пример:

#include <iostream>

int main()
{
    std::cout << u8"это строка6" << std::endl;
    return 0;
}

Устанавливаю в консоли кодовую страницу с помощью следующей команды: chcp 65001

Выполняю программу, и получаю следующий вывод:

��то строка6

Почему первый символ отобразился неправильно? Судя по выводу, при использовании литерала u8, BOM в начало не добавляется. Это кодировка 65001 думает, что в начале идет BOM, пытается прочитать его, а остальное выводит нормально. Тогда хотелось бы найти кодировку UTF-8 без BOM.

Дополнения: Файл сохранен также в utf8 without BOM. Если перенаправить в файл, то запишется нормально. Компилятор Mingw с версией gcc 5.0. Система - Windows.

Kari
  • 103
  • Какой компилятор? Это важно. Система, я так понимаю, Windows? – VladD Jul 07 '15 at 15:14
  • А с _setmode(_fileno(stdout), _O_U8TEXT); работает? (взято отсюда) – VladD Jul 07 '15 at 15:15
  • @VladD в Mingw вообще ничего не выводит – Sergey S Jul 07 '15 at 15:25
  • @VladD, в gnu/linux после компиляции test.cpp командой make test CPPFLAGS=-std=c++0x бинарник при запуске не выдаёт никакик bom-ов. первые байты: d1 8d d1 82 d0 be 20 d1, вполне валидный utf8. – aleksandr barakin Jul 07 '15 at 15:43
  • @andrei.aliashkevich, а если перенаправить вывод программы в файл, запишется тоже с bom-ом? / кстати, а в исходник не «затесалось», случайно, какого-нибудь мусора? – aleksandr barakin Jul 07 '15 at 15:46
  • 3
    Скорее всего исходник сохранен в обычном cp1251. В этом случае буква э кодируется с помощью FD. А так как консоли Вы сказали "будет UTF-8", то она и попыталась распарсить. А FD - это начало 6байтовых последовательностей. Распарсить не удалось, поэтому и вставило ромбик с знаком вопроса. А вот почему дальше нормально - это нужно отдельно исследовать. Может консоль догадалась:) – KoVadim Jul 07 '15 at 16:09
  • @alexanderbarakin BOM тут не причем. std::cout << u8" это строка6" << std::endl; (с пробелом в начале) выводится нормально. – Sergey S Jul 07 '15 at 16:46
  • Ну да, с пробелом нормально выводит. У меня идеи закончились. – andrei.aliashkevich Jul 07 '15 at 16:50
  • Еще одно интересное замечание: если первым символом будет буква латинского алфавита, а следующие, допустим, русского алфавита, то текст выведется нормально. – andrei.aliashkevich Jul 07 '15 at 16:59
  • Проблема в том, что в отличии от других систем, консоль в windows, это не просто файл, принимающий поток байт. И где-то внутри std::cout идет преобразование, либо в oem-кодировку, либо в utf-16 (точно не знаю). При этом никто не запрещает выполнять это преобразование хоть побайтово и использовать в вызовах соответствующих системных API. Поэтому, например, puts("\302\260") выводит символ корректно, а putc('\302', stdout); putc('\260', stdout); - нет. – Sergey S Jul 07 '15 at 18:35

1 Answers1

1

Советую вам не использовать utf-8 при выводе в консоль на Windows (режим utf-8 имеет баги), вместо этого пользоваться "широкими" потоками (подробнее про использование юникода в Windows)

#include <iostream>
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#include <iostream>         // std::wcerr

std::wstring strtows(const std::string &str, UINT codePage)
{
    std::wstring ws;
    int n = MultiByteToWideChar(codePage, 0, str.c_str(), static_cast<int>(str.size()), NULL, 0);
    if (n) {
        ws.resize(n);
        if (MultiByteToWideChar(codePage, 0, str.c_str(), static_cast<int>(str.size()), &ws[0], n) == 0)
            ws.clear();
    }
    return ws;
}

std::string wstostr(const std::wstring &ws, UINT codePage)
{
    // prior to C++11 std::string and std::wstring were not guaranteed to have their memory be contiguous,
    // although all real-world implementations make them contiguous
    std::string str;
    int srcLen = static_cast<int>(ws.size());
    int n = WideCharToMultiByte(codePage, 0, ws.c_str(), srcLen, NULL, 0, 0, NULL);
    if (n) {
        str.resize(n);
        if (WideCharToMultiByte(codePage, 0, ws.c_str(), srcLen, &str[0], n, 0, NULL) == 0)
            str.clear();
    }
    return str;
}

std::string WstringToUtf8(const std::wstring &str)
{
    return wstostr(str, CP_UTF8);
}

std::wstring Utf8ToWstring(const std::string &str)
{

    return strtows(str, CP_UTF8);
}

int main(int argc, char *argv[])
{
    _setmode(_fileno(stdout), _O_U16TEXT);
    std::wcout << Utf8ToWstring(u8"это строка6") << std::endl;
    return 0;
}
Sergey S
  • 5,733
  • Скорее всего придется написать класс (string), представляющий строки в utf8. Может есть какие готовые реализации? – andrei.aliashkevich Jul 07 '15 at 17:01
  • @andrei.aliashkevich общепринятой практикой является хранение utf-8 внутри std::string. Однако при этом нужно быть аккуратным при выводе строк или передаче их в другое api. – Sergey S Jul 07 '15 at 17:03
  • 2
    Хранить в std::string можно, но только хранить. вызов length() уже не вернет количество символов в строке. Получить отдельный символ посредством индекса также не получится. Поэтому и хочу написать класс, который реализует все это. – andrei.aliashkevich Jul 07 '15 at 17:06
  • @andrei.aliashkevich стандарт C++ не гарантирует, что length возвращает кол-во символов. Он возвращает количество байтов. На самом деле, доступ к отдельным символам требуется редко. Чтобы сделать быстрый доступ по индексу, придется использовать шестибайтные символы, что затратно. Проще написать пару доп. функций, например Utf8StrLen и Utf8SubStr, чем писать еще один велосипед. – Sergey S Jul 07 '15 at 17:13
  • 1
    Нуууу, во времена ASCII и два байта на символ считалось «затратно». Не вижу проблем использовать хоть восьмибайтные символы, если это позволит получить произвольный доступ. (По факту, в utf8 произвольного доступа по индексу нету, это чисто потоковый формат.) Память, слава богу, уже давно не узкое место. – VladD Jul 07 '15 at 22:11
  • @VladD в принципе хватит и 4 байт (согласно последнему RFC). Другое дело, что это не решит ни проблему подсчета символов, ни произвольного доступа к произвольному символу, потому что unicode code point != отображаемый символ. – Sergey S Jul 07 '15 at 22:35
  • @zenden2k: Ну, можно ограничить количество акцентов (то есть, по сути дополнительных символов в code point) десятком, и впихнуть всё в 8 байт, например. (В качестве минуса этого подхода, пропадёт «поддержка» ZALGO.) – VladD Jul 07 '15 at 22:40
  • @VladD а если еще вспомнить, что юникодная строка может быть BIDI, и есть 3 типа символов с точки зрения изменения направления, то делать что-либо с этой строкой расхочется. Поэтому, если вы не разрабатываете текстовый редактор, лучше не лезть в эти дебри. – Sergey S Jul 07 '15 at 22:48
  • @zenden2k: unicode code point != отображаемый символ. С этого места поподробней пожалуйста. – andrei.aliashkevich Jul 08 '15 at 06:20
  • @andrei.aliashkevich, из wiki: In character encoding terminology, a code point or code position is any of the numerical values that make up the code space. Many code points represent single characters but they can also have other meanings, such as for formatting. – aleksandr barakin Jul 20 '15 at 22:48