Подтвердить что ты не робот

Проблемы с кодировкой символов Visual Studio С++

Невозможность обернуть голову вокруг этого - настоящий источник стыда...

Я работаю с французской версией Visual Studio (2008) во французской Windows (XP). Французские акценты, помещенные в строки, отправленные в окно вывода, повреждаются. То же самое вводится из окна вывода. Типичная проблема кодирования символов, я вхожу в ANSI, получаю UTF-8 взамен или что-то в этом роде. Какая настройка может гарантировать, что символы остаются в ANSI при отображении строки "жесткого кодирования" в окне вывода?

EDIT:

Пример:

#include <iostream>

int main()
{
std:: cout << "àéêù" << std:: endl;

return 0;
}

Отобразится на выходе:

& oacute; & uacute; & Ucirc; & UML;

(здесь закодировано как HTML для вашего удовольствия)

Мне бы очень хотелось показать:

& agrave; & eacute; & ecirc; & ugrave;

4b9b3361

Ответ 1

Прежде чем идти дальше, я должен упомянуть, что то, что вы делаете, не совместимо с c/С++. Спецификация указывает в 2.2, какие наборы символов действительны в исходном коде. Здесь не так много, и все используемые символы находятся в ascii. Итак... Все ниже - о конкретной реализации (как это происходит, VC2008 на американской машине локали).

Для начала у вас есть 4 символа в строке cout и 4 глифа на выходе. Таким образом, проблема не в кодировке UTF8, поскольку она объединяет несколько символов источника с меньшими глифами.

От исходной строки до дисплея на консоли все эти вещи играют определенную роль:

  • Что кодирует ваш исходный файл (т.е. как ваш файл С++ будет рассматриваться компилятором)
  • Что делает ваш компилятор со строковым литералом, и какой источник кодирования он понимает.
  • как ваш << интерпретирует закодированную строку, которую вы передаете в
  • какая кодировка консоли ожидает
  • как консоль преобразует этот вывод в глиф шрифта.

Теперь...

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

3 еще проще. За исключением управляющих кодов, << просто передает данные вниз для char *.

4 управляется SetConsoleOutputCP. Он должен по умолчанию использовать стандартную кодовую страницу системы. Вы также можете выяснить, какой из них у вас есть с GetConsoleOutputCP (вход управляется по-разному, через SetConsoleCP)

5 - забавный. Я ударил головой, чтобы понять, почему я не мог заставить é правильно отображаться, используя CP1252 (западноевропейские, окна). Оказывается, мой системный шрифт не имеет глифа для этого символа и помогает использовать глиф моей стандартной кодовой страницы (капитал Theta, то же самое я бы получил, если бы я не вызвал SetConsoleOutputCP). Чтобы исправить это, мне пришлось сменить шрифт, который я использую на консолях, на Lucida Console (настоящий шрифт шрифта).

Некоторые интересные вещи, которые я изучил, смотрели на это:

  • Кодирование источника не имеет значения, если компилятор может это понять (в частности, его изменение в UTF8 не изменило сгенерированный код. Моя строка "é" все еще была закодирована с CP1252 как 233 0).
  • VC выбирает кодовую страницу для строковых литералов, которые, как мне кажется, не контролируются.
  • контроль над тем, что показывает консоль, более болезнен, чем ожидалось.

Итак... что это значит для вас? Вот несколько советов:

  • не использовать не-ascii в строковых литералах. Используйте ресурсы, в которых вы управляете кодировкой.
  • убедитесь, что вы знаете, какая кодировка ожидается вашей консолью, и что ваш шрифт имеет глифы для представления отправленных символов.
  • Если вы хотите выяснить, какая кодировка используется в вашем случае, я бы посоветовал напечатать фактическое значение символа как целого. char * a = "é"; std::cout << (unsigned int) (unsigned char) a[0] показывает 233 для меня, что является кодировкой в ​​CP1252.

Кстати, если вы получили "ÓÚÛ¨", а не то, что вы вставили, то похоже, что ваши 4 байта интерпретируются где-то как CP850.

Ответ 2

Попробуйте следующее:

#include <iostream>
#include <locale>

int main()
{
 std::locale::global(std::locale(""));
 std::cout << "àéêù" << std::endl;

 return 0;
}

Ответ 3

Поскольку меня попросили, я сделаю некромантию. Другие ответы были с 2009 года, но эта статья все еще была найдена в поиске, который я сделал в 2018 году. Ситуация сегодня совсем иная. Кроме того, принятый ответ был неполным даже в 2009 году.

Исходный набор символов

Каждый компилятор (включая Microsoft Visual Studio 2008 и более поздние версии, gcc, clang и icc) будет читать исходные файлы UTF-8, которые начинаются с BOM без проблем, а clang не будет читать ничего, кроме UTF-8, поэтому UTF-8 с BOM является наименьшим общим знаменателем для исходных файлов C и C++.

Стандарт языка не говорит, какие исходные наборы символов должен поддерживать компилятор. Некоторые реальные исходные файлы даже сохраняются в наборе символов, несовместимом с ASCII. Microsoft Visual C++ в 2008 году поддерживала исходные файлы UTF-8 с меткой порядка байтов, а также обе формы UTF-16. Без метки порядка байтов предполагается, что файл был закодирован в текущей 8-битной кодовой странице, которая всегда была надмножеством ASCII.

Наборы символов выполнения

В 2012 году компилятор добавил переключатель /utf-8 в CL.EXE. Сегодня он также поддерживает переключатели /source-charset и /execution-charset, а также /validate-charset, чтобы определить, не является ли ваш файл на самом деле UTF-8. На этой странице в MSDN есть ссылка на документацию по поддержке Unicode для каждой версии Visual C++.

Текущие версии стандарта C++ говорят, что компилятор должен иметь как набор символов выполнения, который определяет числовое значение символьных констант, таких как 'a', так и набор широких символов выполнения, который определяет значение констант широких символов, таких как L'é'.

К слову, юрист по языку, в стандарте очень мало требований к тому, как они должны кодироваться, и тем не менее Visual C и C++ могут их нарушить. Он должен содержать около 100 символов, которые не могут иметь отрицательных значений, а кодировка цифр '0' - '9' должна быть последовательной. Ни заглавные, ни строчные буквы не должны быть, потому что они не были на старых мэйнфреймах. (То есть, '0'+9 должен быть таким же, как '9', но сегодня в реальном мире все еще существует компилятор, поведение которого по умолчанию состоит в том, что 'a'+9 не 'j', а '«', и это законно.) Широкий набор символов должен включать базовый набор и иметь достаточно битов для хранения всех символов любой поддерживаемой локали. Каждый основной компилятор поддерживает как минимум одну локаль Unicode и понимает допустимые символы Unicode, указанные в \Uxxxxxxxx, но компилятор, который не может претендовать на соответствие стандарту.

Visual C и C++ нарушают языковой стандарт, создавая их wchar_t UTF-16, который может представлять только некоторые символы в качестве суррогатных пар, когда стандарт говорит, что wchar_t должен быть кодированием фиксированной ширины. Это связано с тем, что Microsoft определила wchar_t как 16-битную ширину еще в 1990-х годах, прежде чем комитет по Юникоду выяснил, что 16-битных будет недостаточно для всего мира, и Microsoft не собирается нарушать Windows API. Он также поддерживает стандартный тип char32_t.

Строковые литералы UTF-8

Третья проблема, которую поднимает этот вопрос, заключается в том, как заставить компилятор кодировать строковый литерал как UTF-8 в памяти. Вы можете написать что-то подобное с C++ 11:

constexpr unsigned char hola_utf8[] = u8"¡Hola, mundo!";

Это закодирует строку в виде представления байтов UTF-8 с нулевым символом в конце независимо от того, является ли исходный набор символов UTF-8, UTF-16, Latin-1, CP1252 или даже IBM EBCDIC 1047 (что глупо теоретический пример, но все же, для обратной совместимости, по умолчанию на компиляторе мэйнфреймов IBM Z-серии). Это эквивалентно инициализации массива с помощью { 0xC2, 0xA1, 'H', /* ... , */ '!', 0 }.

Если было бы слишком неудобно вводить символ или если вы хотите различать внешне идентичные символы, такие как пробел и неразрывный пробел или предварительно составленные и комбинируемые символы, у вас также есть универсальные экранированные символы:

constexpr unsigned char hola_utf8[] = u8"\u00a1Hola, mundo!";

Вы можете использовать их независимо от исходного набора символов и независимо от того, сохраняете ли вы литерал как UTF-8, UTF-16 или UCS-4. Первоначально они были добавлены в C99, но Microsoft поддержала их в Visual Studio 2015.

Редактировать: Как сообщает Мэтью, строки u8" содержат ошибки в некоторых версиях MSVC, включая 19.14. Оказывается, буквальные символы не ASCII, даже если вы указываете /utf-8 или /source-charset:utf-8 /execution-charset:utf-8. Пример кода выше работает должным образом в 19.22.27905.

Однако существует другой способ сделать это, который работал в Visual C или C++ 2008: восьмеричные и шестнадцатеричные коды перехода. Вы бы закодировали литералы UTF-8 в этой версии компилятора с помощью:

const unsigned char hola_utf8[] = "\xC2\xA1Hello, world!";

Ответ 4

Я пробовал этот код:

#include <iostream>
#include <fstream>
#include <sstream>

int main()
{
    std::wstringstream wss;
    wss << L"àéêù";
    std::wstring s = wss.str();
    const wchar_t* p = s.c_str();
    std::wcout << ws.str() << std::endl;

    std::wofstream file("C:\\a.txt");
    file << p << endl;

    return 0;
}

Отладчик показал, что wss, s и p все имели ожидаемые значения (т.е. "àéêù" ), равно как и выходной файл. Однако то, что появилось в консоли, было "...".

Поэтому проблема заключается в консоли Visual Studio, а не в С++. Используя отличный ответ Bahbar, я добавил:

    SetConsoleOutputCP(1252);

в качестве первой строки, а выход консоли появился как следует.

Ответ 5

Использование _setmode() работает¹. и, возможно, это лучше, чем изменение кодовой страницы или установка языкового стандарта, поскольку это фактически сделает вывод вашей программы в Unicode и, следовательно, будет согласованным - независимо от того, какая кодовая страница или языковой стандарт установлены в настоящее время.

Пример:

#include <iostream>
#include <io.h>
#include <fcntl.h>

int wmain()
{
    _setmode( _fileno(stdout), _O_U16TEXT );

    std::wcout << L"àéêù" << std::endl;

    return 0;
}


В Visual Studio убедитесь, что вы настроили свой проект для Unicode (щелкните правой кнопкой мыши Project → Click General → Character Set = Use Unicode Character Set).

Пользователи MinGW:

  1. Определите UNICODE и _UNICODE
  2. Добавьте -finput-charset=iso-8859-1 к параметрам компилятора, чтобы обойти эту ошибку: "преобразование в набор символов выполнения: неверный аргумент"
  3. Добавьте -municode к параметрам компоновщика, чтобы обойти "неопределенную ссылку на" WinMain @16 "(подробнее).


Изменить: Эквивалентный вызов для установки ввода в Unicode: _setmode( _fileno(stdin), _O_U16TEXT );

Редактировать 2: Важная информация, особенно учитывая вопрос использует std::cout. Это не поддерживается В Документах MSDN (выделено мной):

Режим Unicode предназначен для широких функций печати (например, wprintf), а - не поддерживается для узких функций печати. Использование узкой печати функция в потоке режима Юникод вызывает утверждение.

Поэтому не используйте std::cout, когда режим вывода на консоль установлен на _O_U16TEXT; аналогично, не используйте std::cin, если на консольном входе _O_U16TEXT. Вы должны использовать широкую версию этих средств (std::wcout, std::wcin).
И обратите внимание, что смешивание cout и wcout в одном и том же выводе недопустимо (но я считаю, что это работает, если вы вызываете flush() и затем _setmode() до переключения между узкой и широкой операциями).

Ответ 6

//Save As Windows 1252
#include<iostream>
#include<windows.h>

int main()
{
    SetConsoleOutputCP(1252);
    std:: cout << "àéêù" << std:: endl;
}

Visual Studio не поддерживает UTF 8 для С++, но частично поддерживает C:

//Save As UTF8 without signature
#include<stdio.h>
#include<windows.h>

int main()
{
    SetConsoleOutputCP(65001);
    printf("àéêù\n");
}

Ответ 7

Убедитесь, что вы не забыли изменить шрифт консоли Lucida Consolas, как упоминалось Bahbar: это было важно в моем случае (французский выигрыш 7 64 бит с VC 2012).

Затем, как упоминалось другими, используйте SetConsoleOutputCP (1252) для С++, но он может выйти из строя в зависимости от доступных страниц, поэтому вы можете использовать GetConsoleOutputCP(), чтобы проверить, что это сработало или, по крайней мере, проверить, что SetConsoleOutputCP (1252) возвращает ноль, Изменение глобальной локали также работает (по какой-то причине нет необходимости делать cout.imbue(locale()), но это может сломать некоторые librairies!

В C, SetConsoleOutputCP (65001); или подход на основе локали работал у меня , как только я сохранил исходный код как UTF8 без подписи (прокрутка вниз, выбор sans-подписи ниже в списке страниц).

Вход с помощью SetConsoleCP (65001); не удалось мне, по-видимому, из-за плохой реализации страницы 65001 в окнах. Локальный подход тоже сработал как на C, так и на С++. Требуется более активное решение, не полагающееся на собственные символы, но на wchar_t.