Проблемы с кодировкой символов в Visual Studio C ++ - PullRequest
15 голосов
/ 07 декабря 2009

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

Я работаю с французской версией 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)

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

* * & 1 022 agrave; & eacute; & ecirc; & ugrave; * * тысячу двадцать-три

Ответы [ 7 ]

14 голосов
/ 08 декабря 2009

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

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

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

  1. В какой кодировке находится ваш исходный файл (т.е. как ваш C ++ файл будет виден компилятором)
  2. Что делает ваш компилятор со строковым литералом и какую кодировку источника он понимает
  3. как ваш << интерпретирует кодированную строку, которую вы передаете
  4. какую кодировку ожидает консоль
  5. как консоль переводит этот вывод в глиф шрифта.

Сейчас ...

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

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

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

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

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

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

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

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

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

6 голосов
/ 14 января 2011

Попробуйте это:

#include <iostream>
#include <locale>

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

 return 0;
}
3 голосов
/ 23 апреля 2018

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

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

Каждый компилятор (включая Microsoft Visual Studio 2008 и более поздние версии, gcc, clang и icc) будет читать исходные файлы UTF-8, которые начинаются с спецификации, без проблем, а clang не будет читать ничего, кроме UTF-8, поэтому UTF-8 с спецификацией - самый низкий общий знаменатель для исходных файлов 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. Однако существует другой способ сделать это, который работал в Visual C или C ++ 2008: восьмеричные и шестнадцатеричные коды перехода. Вы бы закодировали литералы UTF-8 в этой версии компилятора с помощью:

const unsigned char hola_utf8[] = "\xC2\xA1Hello, world!";
3 голосов
/ 07 декабря 2009

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

#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, а не в C ++. Используя отличный ответ Бахбара, я добавил:

    SetConsoleOutputCP(1252);

в качестве первой строки, и вывод на консоль затем появился, как и должно.

2 голосов
/ 31 октября 2016

Использование _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 -> Нажмите General -> Набор символов = Использовать Unicode Набор символов ).

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

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

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

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

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

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

1 голос
/ 28 сентября 2013
//Save As Windows 1252
#include<iostream>
#include<windows.h>

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

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

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

int main()
{
    SetConsoleOutputCP(65001);
    printf("àéêù\n");
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...